heap_dump 0.0.28 → 0.0.29

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -24,6 +24,7 @@ Or install it yourself as:
24
24
 
25
25
  ## Usage
26
26
 
27
+ ### Dumping heap references
27
28
  In your code call:
28
29
 
29
30
  ```ruby
@@ -34,6 +35,77 @@ HeapDump.dump
34
35
  this will run GC and then create a dump.json with live heap contents.
35
36
  Json contains one object per line, thus can be easily grepped.
36
37
 
38
+ #### Output example/format
39
+
40
+ Format is not stable yet, but looks like this:
41
+
42
+ ```json
43
+
44
+ [{"id":"_ROOTS_","stack_and_registers":[70313628419480,70313628419480,70313628419480,"trace",70313627751860],"classes":[70313627319820,70313628530860]}
45
+ ,{"id":70365602702620,"bt":"T_ARRAY","val":[">=",70365602705060]}
46
+ ,{"id":70365602847060,"bt":"T_ARRAY","val":[]}
47
+ ,{"id":70365602702660,"bt":"T_DATA","type_name":"iseq","size":564,"name":"activate_spec","filename":"/Users/vasfed/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/site_ruby/1.9.1/rubygems.rb","line":485,"type":"method","refs_array_id":70365602847060,"coverage":null,"klass":70365602821240,"cref_stack":70365602848300,"defined_method_id":12672}
48
+ ,{"id":70365602702680,"bt":"T_STRING","val":"activate_spec"}
49
+ ,{"id":70365602821260,"bt":"T_HASH","val":{"EXEEXT":"","RUBY_SO_NAME":"ruby.1.9.1","arch":"x86_64-darwin11.2.0","bindir":70365603049640,"libdir":70365603050600,"ruby_install_name":"ruby","ruby_version":"1.9.1","rubylibprefix":70365603112080,"sitedir":70365603112440,"sitelibdir":70365603048920,"datadir":70365603049880,"vendordir":70365603112500,"vendorlibdir":70365603048800}}
50
+ ,{"id":70365602712560,"bt":"T_CLASS","name":"URI::HTTPS","methods":{},"ivs":{"__classpath__":"URI::HTTPS","DEFAULT_PORT":443},"super":70365602771440}
51
+ ,{"id":70365602771400,"bt":"T_CLASS","name":"Class","methods":{"build":70365602782860},"ivs":{"__attached__":70365602771440},"super":70365611597900}
52
+ ,{"id":70365602717060,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70365602712440,"iseq":{"id":70365600821896,"name":"block in <class:FileList>","filename":"/Users/vasfed/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/file_list.rb","line":743,"type":"block","refs_array_id":70365611724600,"coverage":null,"klass":null,"cref_stack":70365611799480,"defined_method_id":0}}
53
+ ,{"id":70365603045160,"bt":"T_DATA","type_name":"VM/env","size":128,"refs":[]}
54
+ ,{"id":70365613258980,"bt":"T_ICLASS","name":"Object","methods":{"==":"(CFUNC)",">":"(CFUNC)",">=":"(CFUNC)","<":"(CFUNC)","<=":"(CFUNC)","between?":"(CFUNC)"},"ivs":{"__classid__":"Comparable"},"super":70365613259120}
55
+ ,{"id":70151795145340,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70151795224840,"iseq":{"id":70151828020360,"name":"block in subscribe","filename":"/Users/vasfed/acceptor.rb","line":91,"type":"block","refs_array_id":70151796420080,"coverage":null,"klass":null,"cref_stack":70151796421080,"defined_method_id":0}}
56
+ ,{"id":70151795224840,"bt":"T_DATA","type_name":"VM/env","size":120,"refs":[70151806738200,70151796176180,"string1",null,0,70151795224840,null]}
57
+ ]
58
+ ```
59
+ etc.
60
+
61
+ bt field is ruby builtin type name.
62
+
63
+ Where available - val/refs/ivs/etc. field present with hash/array of references.
64
+ Long numbers usually are object ids.
65
+
66
+ ### Counting objects in heap
67
+
68
+ Also heap_dump now includes an object counter, like `ObjectSpace.count_objects`, but capable of counting objects from your namespace
69
+
70
+ ```ruby
71
+
72
+ HeapDump.count_objects :YourNameSpace # => json string
73
+ ```
74
+
75
+ which results in something like:
76
+
77
+ ```json
78
+
79
+ {
80
+ "total_slots": 56419,
81
+ "free_slots": 12384,
82
+ "basic_types": {
83
+ "T_OBJECT": 1945,
84
+ "T_CLASS": 931,
85
+ "T_MODULE": 68,
86
+ "T_FLOAT": 24,
87
+ "T_STRING": 26557,
88
+ "T_REGEXP": 346,
89
+ "T_ARRAY": 6556,
90
+ "T_HASH": 159,
91
+ "T_STRUCT": 45,
92
+ "T_BIGNUM": 2,
93
+ "T_FILE": 6,
94
+ "T_DATA": 3516,
95
+ "T_MATCH": 15,
96
+ "T_COMPLEX": 1,
97
+ "T_RATIONAL": 10,
98
+ "T_NODE": 3754,
99
+ "T_ICLASS": 100
100
+ },
101
+ "user_types": {
102
+ "YourNameSpace::B": 2,
103
+ "YourNameSpace::A": 3
104
+ }
105
+ }
106
+ ```
107
+
108
+
37
109
  ### Injecting into live process via GDB
38
110
 
39
111
  For long-running applications common and recommended usecase is to have some kind of custom action or signal handler in app itself that invokes dump.
@@ -71,34 +143,6 @@ cat dump.json | sed 's/^[,\[]//;s/\]$//;s/^{"id"/{"_id"/' | mongoimport -d datab
71
143
  Note that even small dumps usually contain a few hundred thousands objects, so do not forget to add some indexes.
72
144
 
73
145
 
74
- ### Output example/format
75
-
76
- Format is not stable yet, but looks like this:
77
-
78
- ```json
79
-
80
- [{"id":"_ROOTS_","stack_and_registers":[70313628419480,70313628419480,70313628419480,"trace",70313627751860],"classes":[70313627319820,70313628530860]}
81
- ,{"id":70365602702620,"bt":"T_ARRAY","val":[">=",70365602705060]}
82
- ,{"id":70365602847060,"bt":"T_ARRAY","val":[]}
83
- ,{"id":70365602702660,"bt":"T_DATA","type_name":"iseq","size":564,"name":"activate_spec","filename":"/Users/vasfed/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/site_ruby/1.9.1/rubygems.rb","line":485,"type":"method","refs_array_id":70365602847060,"coverage":null,"klass":70365602821240,"cref_stack":70365602848300,"defined_method_id":12672}
84
- ,{"id":70365602702680,"bt":"T_STRING","val":"activate_spec"}
85
- ,{"id":70365602821260,"bt":"T_HASH","val":{"EXEEXT":"","RUBY_SO_NAME":"ruby.1.9.1","arch":"x86_64-darwin11.2.0","bindir":70365603049640,"libdir":70365603050600,"ruby_install_name":"ruby","ruby_version":"1.9.1","rubylibprefix":70365603112080,"sitedir":70365603112440,"sitelibdir":70365603048920,"datadir":70365603049880,"vendordir":70365603112500,"vendorlibdir":70365603048800}}
86
- ,{"id":70365602712560,"bt":"T_CLASS","name":"URI::HTTPS","methods":{},"ivs":{"__classpath__":"URI::HTTPS","DEFAULT_PORT":443},"super":70365602771440}
87
- ,{"id":70365602771400,"bt":"T_CLASS","name":"Class","methods":{"build":70365602782860},"ivs":{"__attached__":70365602771440},"super":70365611597900}
88
- ,{"id":70365602717060,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70365602712440,"iseq":{"id":70365600821896,"name":"block in <class:FileList>","filename":"/Users/vasfed/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/file_list.rb","line":743,"type":"block","refs_array_id":70365611724600,"coverage":null,"klass":null,"cref_stack":70365611799480,"defined_method_id":0}}
89
- ,{"id":70365603045160,"bt":"T_DATA","type_name":"VM/env","size":128,"refs":[]}
90
- ,{"id":70365613258980,"bt":"T_ICLASS","name":"Object","methods":{"==":"(CFUNC)",">":"(CFUNC)",">=":"(CFUNC)","<":"(CFUNC)","<=":"(CFUNC)","between?":"(CFUNC)"},"ivs":{"__classid__":"Comparable"},"super":70365613259120}
91
- ,{"id":70151795145340,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70151795224840,"iseq":{"id":70151828020360,"name":"block in subscribe","filename":"/Users/vasfed/acceptor.rb","line":91,"type":"block","refs_array_id":70151796420080,"coverage":null,"klass":null,"cref_stack":70151796421080,"defined_method_id":0}}
92
- ,{"id":70151795224840,"bt":"T_DATA","type_name":"VM/env","size":120,"refs":[70151806738200,70151796176180,"string1",null,0,70151795224840,null]}
93
- ]
94
- ```
95
- etc.
96
-
97
- bt field is ruby builtin type name.
98
-
99
- Where available - val/refs/ivs/etc. field present with hash/array of references.
100
- Long numbers usually are object ids.
101
-
102
146
  ## What may leak
103
147
 
104
148
  ### Brief Ruby GC decription
@@ -126,6 +170,14 @@ Obvious references: from variables, instance variables (including class), arrays
126
170
  Less obvious: method closures. These are stored in T_DATAs with 'VM/env' type.
127
171
  Latest version of heap_dump allows to trace such references: search for a env, by it's id you can find it's owner iseq, which usually has file and line number where block/lambda/proc was created.
128
172
 
173
+ ## Hints on finding leaks
174
+
175
+ Usually it's a good practice first to detect leaking type by running several `HeapDump.count_objects` while making some load on your app and comparing results. Note objects that may be reasons for others to linger in memory according to your architecture (for example - something like "session"/"incoming connection"/"delayed job" etc.).
176
+
177
+ Then make a full dump and inspect references to those objects.
178
+
179
+ Note that heapdump operates only on process it has been called on, so if you have multiple workers (Unicorn/Rainbows/Passenger spawned processes etc.) - you may run into a situation when request for dump is not routed to process you're interested in.
180
+
129
181
  ## Contributing
130
182
 
131
183
  1. Fork it
data/Rakefile CHANGED
@@ -37,8 +37,11 @@ task :test => :compile do
37
37
  Fiber.yield e
38
38
  fiber_var = :some_fiber_var3
39
39
  }.resume
40
- puts "Dumping..."
40
+ $some_global = "value_global"
41
+ puts "Dumping...(rake)"
42
+ HeapDump.verbose = true
41
43
  HeapDump.dump
44
+ puts "Done"
42
45
  end
43
46
 
44
47
  task :default => :test
@@ -47,6 +47,7 @@
47
47
 
48
48
  // simple test - rake compile && bundle exec ruby -e 'require "heap_dump"; HeapDump.dump'
49
49
 
50
+ #include <dlfcn.h>
50
51
 
51
52
  #ifdef HAVE_GC_INTERNAL_H
52
53
  #include "gc_internal.h"
@@ -64,12 +65,16 @@ static VALUE rb_mHeapDumpModule;
64
65
  static ID classid;
65
66
 
66
67
  //shortcuts to yajl
67
- #define yg_string(str,len) yajl_gen_string(ctx->yajl, str, len)
68
+ #define YAJL ctx->yajl
69
+ #define yg_string(str,len) yajl_gen_string(YAJL, str, len)
68
70
  #define yg_cstring(str) yg_string(str, (unsigned int)strlen(str))
69
71
  #define yg_rstring(str) yg_string(RSTRING_PTR(str), (unsigned int)RSTRING_LEN(str))
70
- #define yg_int(i) yajl_gen_integer(ctx->yajl, i)
71
- #define yg_double(d) (yajl_gen_double(ctx->yajl, d)==yajl_gen_invalid_number? yg_cstring("inf|NaN") : true)
72
- #define yg_null() yajl_gen_null(ctx->yajl)
72
+ #define yg_int(i) yajl_gen_integer(YAJL, i)
73
+ #define yg_double(d) (yajl_gen_double(YAJL, d)==yajl_gen_invalid_number? yg_cstring("inf|NaN") : true)
74
+ #define yg_null() yajl_gen_null(YAJL)
75
+ #define yg_bool(b) yajl_gen_bool(YAJL, b);
76
+
77
+ #define yg_funcaddr(addr) yg_funcaddr_real(ctx, addr)
73
78
 
74
79
  //#define yg_id(obj) yg_int(NUM2LONG(rb_obj_id(obj)))
75
80
  #define yg_id(obj) yg_id1(obj,ctx)
@@ -82,10 +87,10 @@ static ID classid;
82
87
  #define ygh_cstring(key,str) {yg_cstring(key); yg_cstring(str);}
83
88
  #define ygh_rstring(key,str) {yg_cstring(key); yg_rstring(str);}
84
89
 
85
- #define yg_map() yajl_gen_map_open(ctx->yajl);
86
- #define yg_map_end() yajl_gen_map_close(ctx->yajl);
87
- #define yg_array() yajl_gen_array_open(ctx->yajl);
88
- #define yg_array_end() yajl_gen_array_close(ctx->yajl);
90
+ #define yg_map() yajl_gen_map_open(YAJL);
91
+ #define yg_map_end() yajl_gen_map_close(YAJL);
92
+ #define yg_array() yajl_gen_array_open(YAJL);
93
+ #define yg_array_end() yajl_gen_array_close(YAJL);
89
94
 
90
95
 
91
96
  // context for objectspace_walker callback
@@ -138,6 +143,15 @@ static inline const char* rb_builtin_type(VALUE obj){
138
143
  #define true 1
139
144
  #define false 0
140
145
 
146
+ static void yg_funcaddr_real(walk_ctx_t* ctx, void* addr){
147
+ Dl_info info;
148
+ if(dladdr(addr, &info) && info.dli_sname){
149
+ yg_cstring(info.dli_sname);
150
+ } else {
151
+ yg_cstring("(unknown)");
152
+ }
153
+ }
154
+
141
155
  //FIXME: handle non-ids?
142
156
  static void yg_id1(VALUE obj, walk_ctx_t* ctx){
143
157
  if(!obj) {
@@ -145,23 +159,17 @@ static void yg_id1(VALUE obj, walk_ctx_t* ctx){
145
159
  return;
146
160
  }
147
161
  if (IMMEDIATE_P(obj)) {
148
- //printf("immediate\n");
149
- if (FIXNUM_P(obj)) { /*ignore immediate fixnum*/
150
- //fixme: output some readable info
151
- //yajl_gen_null(ctx->yajl);
162
+ if (FIXNUM_P(obj)) {
152
163
  yg_int(FIX2LONG(obj));
153
164
  return;
154
165
  }
155
166
  if (obj == Qtrue){ yajl_gen_bool(ctx->yajl, true); return; }
156
167
  if (SYMBOL_P(obj)) {
157
- //printf("symbol\n");
158
168
  yg_cstring(rb_id2name(SYM2ID(obj)));
159
- //printf("symbol %s\n", rb_id2name(SYM2ID(obj)));
160
169
  return;
161
170
  }
162
171
  if (obj == Qundef) { yg_cstring("(undef)"); return; }
163
172
 
164
- printf("immediate p %p?\n", (void*)obj);
165
173
  yg_cstring("(unknown)");
166
174
  return;
167
175
  } else /*non-immediate*/ {
@@ -174,26 +182,11 @@ static void yg_id1(VALUE obj, walk_ctx_t* ctx){
174
182
  yajl_gen_bool(ctx->yajl, false);
175
183
  return;
176
184
  }
177
- //printf("non r-test\n");
178
185
  }
179
186
  }
180
187
 
181
- //25116(0x621c aka 0b110001000011100), 28(0x) - wtf?
182
- //28 = 0x1c
183
- //1c= TNODE? or other flags combination
184
-
185
- //also 30(x1e) ? - some internal symbols?
186
-
187
- // if((obj & ~(~(VALUE)0 << RUBY_SPECIAL_SHIFT)) == 0x1c){
188
- // printf("!!!!! special shift flags is 0x1c: %p\n", obj);
189
- // yg_cstring("(unknown or internal 1c)");
190
- // return;
191
- // }
192
-
193
188
  if(BUILTIN_TYPE(obj) == T_STRING && (!(RBASIC(obj)->flags & RSTRING_NOEMBED))){
194
- //printf("embedded string\n");
195
- //yajl_gen_null(ctx->yajl);
196
-
189
+ //embedded string
197
190
  if(rb_enc_get_index(obj) == rb_usascii_encindex())
198
191
  yg_rstring(obj);
199
192
  else{
@@ -319,14 +312,14 @@ static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){
319
312
  return; //goto again;
320
313
 
321
314
  case NODE_SCOPE: /* 2,3 */ //ANN("format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body");
322
- //printf("node scope\n");
315
+ //actually this is not present in live ruby 1.9+
323
316
  if(obj->nd_tbl){
324
317
  ID *tbl = RNODE(obj)->nd_tbl;
325
318
  unsigned long i = 0, size = tbl[0];
326
319
  tbl++;
327
320
  for (; i < size; i++) {
328
321
  //TODO: dump local var names?
329
- //printf("%s\n", rb_id2name(tbl[i]));
322
+ // rb_id2name(tbl[i])...
330
323
  //yg_id(tbl[i]); //FIXME: these are ids, not values
331
324
  }
332
325
  }
@@ -399,7 +392,6 @@ static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){
399
392
 
400
393
  case NODE_MEMO:
401
394
  yg_id((VALUE)obj->u1.node);
402
- //printf("MEMO NODE: %p %p %p\n", obj->u1.node, obj->u2.node, obj->u3.node);
403
395
  break;
404
396
 
405
397
  //not implemented:
@@ -416,14 +408,15 @@ static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){
416
408
 
417
409
  //iteration func - blocks,procs,lambdas etc:
418
410
  case NODE_IFUNC: //NEN_CFNC, NEN_TVAL, NEN_STATE? / u2 seems to be data for func(context?)
419
- //printf("IFUNC NODE: %p %p %p\n", obj->nd_cfnc, obj->u2.node, (void*)obj->nd_aid /*u3 - aid id- - aka frame_this_func?*/);
411
+ {
412
+ //find in symbol table, if present:
413
+ yg_funcaddr(obj->nd_cfnc);
414
+ }
420
415
  if(is_in_heap(obj->u2.node, 0)){
421
- //printf("in heap: %p\n", obj->u2.node);
422
416
  //TODO: do we need to dump it inline?
423
417
  yg_id((VALUE)obj->u2.node);
424
418
  }
425
419
  if(is_in_heap( (void*)obj->nd_aid, 0)){
426
- //printf("in heap: %p\n", (void*)obj->nd_aid);
427
420
  yg_id(obj->nd_aid);
428
421
  }
429
422
  break;
@@ -432,11 +425,7 @@ static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){
432
425
  case NODE_BEGIN: break;
433
426
  default: /* unlisted NODE */
434
427
  //FIXME: check pointers!
435
-
436
- {
437
- printf("UNKNOWN NODE TYPE %d(%s): %p %p %p\n", nd_type(obj), node_type_name(obj), (void*)obj->u1.node, (void*)obj->u2.node, (void*)obj->u3.node);
438
- }
439
-
428
+ {}
440
429
  // if (is_in_heap(obj->as.node.u1.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); }
441
430
  // if (is_in_heap(obj->as.node.u2.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u2.node, lev); }
442
431
  // if (is_in_heap(obj->as.node.u3.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u3.node, lev); }
@@ -459,11 +448,6 @@ static inline void dump_node(NODE* obj, walk_ctx_t *ctx){
459
448
 
460
449
  static int
461
450
  dump_keyvalue(st_data_t key, st_data_t value, walk_ctx_t *ctx){
462
- if ((VALUE)key == Qundef){
463
- printf("undef key!\n");
464
- // return ST_CONTINUE;
465
- }
466
-
467
451
  if(!key || (VALUE)key == Qnil){
468
452
  yg_cstring("___null_key___"); //TODO: just ""?
469
453
  } else {
@@ -477,7 +461,7 @@ dump_keyvalue(st_data_t key, st_data_t value, walk_ctx_t *ctx){
477
461
  buf[sizeof(buf)-1] = 0;
478
462
  switch(type){
479
463
  case T_FIXNUM:
480
- snprintf(buf, sizeof(buf)-1, "%d", FIX2INT(key));
464
+ snprintf(buf, sizeof(buf)-1, "%ld", FIX2LONG(key));
481
465
  break;
482
466
  case T_FLOAT:
483
467
  snprintf(buf, sizeof(buf)-1, "%lg", NUM2DBL(key));
@@ -508,22 +492,17 @@ static void dump_method_definition_as_value(const rb_method_definition_t *def, w
508
492
  yajl_gen_null(ctx->yajl);
509
493
  return;
510
494
  }
511
- //printf("mdef %d\n", def->type);
512
495
 
513
496
  switch (def->type) {
514
497
  case VM_METHOD_TYPE_ISEQ:
515
- //printf("method iseq %p\n", def->body.iseq);
516
- //printf("self %p\n", def->body.iseq->self);
517
498
  yg_id(def->body.iseq->self);
518
499
  break;
519
500
  case VM_METHOD_TYPE_CFUNC: yg_cstring("(CFUNC)"); break;
520
501
  case VM_METHOD_TYPE_ATTRSET:
521
502
  case VM_METHOD_TYPE_IVAR:
522
- //printf("method ivar\n");
523
503
  yg_id(def->body.attr.location);
524
504
  break;
525
505
  case VM_METHOD_TYPE_BMETHOD:
526
- //printf("method binary\n");
527
506
  yg_id(def->body.proc);
528
507
  break;
529
508
  case VM_METHOD_TYPE_ZSUPER: yg_cstring("(ZSUPER)"); break;
@@ -546,7 +525,6 @@ static int dump_method_entry_i(ID key, const rb_method_entry_t *me, st_data_t da
546
525
  }
547
526
 
548
527
  //gc_mark(objspace, me->klass, lev);?
549
- //printf("method entry\n");
550
528
  dump_method_definition_as_value(me->def, ctx);
551
529
  return ST_CONTINUE;
552
530
  }
@@ -556,24 +534,16 @@ static int dump_iv_entry(ID key, VALUE value, walk_ctx_t *ctx){
556
534
  if(key_str)
557
535
  yg_cstring(key_str);
558
536
  else{
559
- // printf("Null key! %d\n", key);
537
+ // cannot use yg_null() - keys must be strings
538
+ //TODO: just ""?
560
539
  yg_cstring("___null_key___");
561
- //yg_null();
562
540
  }
563
541
  yg_id(value);
564
542
  return ST_CONTINUE;
565
543
  }
566
544
 
567
545
  static int dump_const_entry_i(ID key, const rb_const_entry_t *ce, walk_ctx_t *ctx){
568
-
569
- //was(ID key, VALUE value, walk_ctx_t *ctx){
570
-
571
- printf("const entry\n");
572
-
573
546
  VALUE value = ce->value;
574
- //file = ce->file
575
-
576
- printf("const key %p\n", (void*)key);
577
547
  yg_cstring(rb_id2name(key));
578
548
  yg_id(value);
579
549
  return ST_CONTINUE;
@@ -591,14 +561,13 @@ static const char* iseq_type(VALUE type){
591
561
  case ISEQ_TYPE_MAIN: return "main";
592
562
  case ISEQ_TYPE_DEFINED_GUARD: return "defined_guard";
593
563
  }
594
- printf("unknown iseq type %p!\n", (void*)type);
595
- return "unknown";
564
+ return "unknown_iseq";
596
565
  }
597
566
 
598
567
  static void dump_iseq(const rb_iseq_t* iseq, walk_ctx_t *ctx){
599
568
  if(iseq->name) ygh_rstring("name", iseq->name);
600
569
  if(iseq->filename) ygh_rstring("filename", iseq->filename);
601
- ygh_int("line", FIX2INT(iseq->line_no));
570
+ ygh_int("line", FIX2LONG(iseq->line_no));
602
571
 
603
572
  //if(iseq->type != 25116) //also 28 in mark_ary
604
573
  ygh_cstring("type", iseq_type(iseq->type));
@@ -626,6 +595,21 @@ static void dump_iseq(const rb_iseq_t* iseq, walk_ctx_t *ctx){
626
595
  ygh_id("cd_err_info", compile_data->err_info);
627
596
  ygh_id("cd_catch_table_ary", compile_data->catch_table_ary);
628
597
  }
598
+
599
+ if(iseq->local_table_size > 0){
600
+ yg_cstring("local_table");
601
+ yg_array();
602
+ int i;
603
+ for(i = 0; i < iseq->local_table_size; i++){
604
+ char* name = rb_id2name(iseq->local_table[i]);
605
+ if(name){
606
+ yg_cstring(name);
607
+ } else {
608
+ yg_cstring("(unnamed)");
609
+ }
610
+ }
611
+ yg_array_end();
612
+ }
629
613
  }
630
614
 
631
615
  static void dump_block(const rb_block_t* block, walk_ctx_t *ctx){
@@ -758,26 +742,20 @@ static void dump_data_if_known(VALUE obj, walk_ctx_t *ctx){
758
742
  }
759
743
 
760
744
  if(!strcmp("method", typename)){
761
- //printf("method\n");
762
745
  struct METHOD *data = RTYPEDDATA_DATA(obj);
763
- //printf("method %p: %p %p\n", data, data->rclass, data->recv);
764
746
  ygh_id("rclass", data->rclass);
765
747
  ygh_id("recv", data->recv);
766
748
  ygh_int("method_id", data->id);
767
749
 
768
750
  yg_cstring("method");
769
751
  if(METHOD_DEFINITIONP(data)){
770
- //printf("methof def %p\n", &data->me);
771
752
  dump_method_definition_as_value(METHOD_DEFINITIONP(data), ctx);
772
- //printf("meth end\n");
773
753
  }
774
754
  return;
775
755
  }
776
756
 
777
757
  if(!strcmp("binding", typename)){
778
- //printf("binding\n");
779
758
  rb_binding_t *bind = RTYPEDDATA_DATA(obj);
780
- //printf("binding %p\n", bind);
781
759
  if(!bind) return;
782
760
  ygh_id("env", bind->env);
783
761
  ygh_id("filename", bind->filename);
@@ -929,9 +907,9 @@ static void dump_data_if_known(VALUE obj, walk_ctx_t *ctx){
929
907
 
930
908
  static VALUE rb_class_real_checked(VALUE cl)
931
909
  {
932
- if (cl == 0)
910
+ if (cl == 0 || IMMEDIATE_P(cl))
933
911
  return 0;
934
- while ((RBASIC(cl)->flags & FL_SINGLETON) || BUILTIN_TYPE(cl) == T_ICLASS) {
912
+ while (cl && ((RBASIC(cl)->flags & FL_SINGLETON) || BUILTIN_TYPE(cl) == T_ICLASS)) {
935
913
  if(RCLASS_EXT(cl) && RCLASS_SUPER(cl)){
936
914
  cl = RCLASS_SUPER(cl);
937
915
  } else {
@@ -1084,14 +1062,13 @@ static inline void walk_live_object(VALUE obj, walk_ctx_t *ctx){
1084
1062
  yajl_gen_map_close(ctx->yajl);
1085
1063
  }
1086
1064
 
1087
- #if 0
1065
+ #ifdef HAVE_CONSTANT_H
1088
1066
  // this is for 1.9.3 or so - where rb_classext_t has const_tbl
1089
1067
  if(RCLASS_CONST_TBL(obj)){
1090
1068
  yg_cstring("consts");
1091
- yajl_gen_map_open(ctx->yajl);
1092
- flush_yajl(ctx); //for debug only
1069
+ yg_map();
1093
1070
  st_foreach(RCLASS_CONST_TBL(obj), dump_const_entry_i, (st_data_t)ctx);
1094
- yajl_gen_map_close(ctx->yajl);
1071
+ yg_map_end();
1095
1072
  }
1096
1073
  #endif
1097
1074
 
@@ -1115,7 +1092,7 @@ static inline void walk_live_object(VALUE obj, walk_ctx_t *ctx){
1115
1092
  break;
1116
1093
 
1117
1094
  case T_FIXNUM:
1118
- ygh_int("val", NUM2LONG(obj));
1095
+ ygh_int("val", FIX2LONG(obj));
1119
1096
  break;
1120
1097
  case T_FLOAT:
1121
1098
  ygh_double("val", RFLOAT_VALUE(obj));
@@ -1271,7 +1248,7 @@ static inline int is_in_heap(void *ptr, void* osp){
1271
1248
 
1272
1249
 
1273
1250
  static int
1274
- dump_backtrace(void* data, VALUE file, int line, VALUE method)
1251
+ dump_backtrace(void* data, VALUE file, int line, VALUE method, int argc, VALUE* argv)
1275
1252
  {
1276
1253
  walk_ctx_t *ctx = data;
1277
1254
  yg_map();
@@ -1287,54 +1264,76 @@ dump_backtrace(void* data, VALUE file, int line, VALUE method)
1287
1264
  //fprintf(fp, "\tfrom %s:%d:in `%s'\n", filename, line, RSTRING_PTR(method));
1288
1265
  ygh_rstring("method", method);
1289
1266
  }
1267
+ ygh_int("argc", argc);
1268
+ if(argc > 0){
1269
+ yg_cstring("argv");
1270
+ yg_array();
1271
+ int i;
1272
+ for(i = 0; i < argc; i++)
1273
+ yg_id(argv[i]);
1274
+ yg_array_end();
1275
+ }
1290
1276
  yg_map_end();
1291
1277
  return FALSE;
1292
1278
  }
1293
1279
 
1294
- //TODO: autogen, this func is just copied from vm.c
1295
- //typedef int rb_backtrace_iter_func(void *, VALUE, int, VALUE);
1280
+ typedef int (rb_backtrace_iter_ext_func)(void *arg, VALUE file, int line, VALUE method_name, int argc, VALUE* argv);
1281
+
1282
+ // copied from ruby_ext_backtrace
1296
1283
  static int
1297
- vm_backtrace_each(const rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg)
1284
+ vm_backtrace_each_ext(rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_ext_func *iter, void *arg)
1298
1285
  {
1299
- const rb_control_frame_t *limit_cfp = th->cfp;
1300
- const rb_control_frame_t *cfp = (void *)(th->stack + th->stack_size);
1301
- VALUE file = Qnil;
1302
- int line_no = 0;
1303
-
1304
- cfp -= 2;
1305
- while (lev-- >= 0) {
1306
- if (++limit_cfp > cfp) {
1307
- return FALSE;
1286
+ const rb_control_frame_t *limit_cfp = th->cfp;
1287
+ const rb_control_frame_t *cfp = (void *)(th->stack + th->stack_size);
1288
+ VALUE file = Qnil;
1289
+ int line_no = 0;
1290
+
1291
+ cfp -= 2;
1292
+ //skip lev frames:
1293
+ while (lev-- >= 0) {
1294
+ if (++limit_cfp > cfp)
1295
+ return FALSE;
1308
1296
  }
1309
- }
1310
- if (init) (*init)(arg);
1311
- limit_cfp = RUBY_VM_NEXT_CONTROL_FRAME(limit_cfp);
1312
- if (th->vm->progname) file = th->vm->progname;
1313
- while (cfp > limit_cfp) {
1314
- if (cfp->iseq != 0) {
1315
- if (cfp->pc != 0) {
1316
- rb_iseq_t *iseq = cfp->iseq;
1317
-
1318
- line_no = rb_vm_get_sourceline(cfp);
1319
- file = iseq->filename;
1320
- if ((*iter)(arg, file, line_no, iseq->name)) break;
1321
- }
1322
- }
1323
- else if (RUBYVM_CFUNC_FRAME_P(cfp)) {
1324
- ID id;
1325
-
1326
1297
 
1327
- if (NIL_P(file)) file = ruby_engine_name;
1328
- if (cfp->me->def)
1329
- id = cfp->me->def->original_id;
1330
- else
1331
- id = cfp->me->called_id;
1332
- if (id != ID_ALLOCATOR && (*iter)(arg, file, line_no, rb_id2str(id)))
1333
- break;
1298
+ if (init) (*init)(arg);
1299
+
1300
+ limit_cfp = RUBY_VM_NEXT_CONTROL_FRAME(limit_cfp);
1301
+ if (th->vm->progname) file = th->vm->progname;
1302
+
1303
+ while (cfp > limit_cfp) {
1304
+ if (cfp->iseq != 0) {
1305
+ if (cfp->pc != 0) {
1306
+ rb_iseq_t *iseq = cfp->iseq;
1307
+
1308
+ line_no = rb_vm_get_sourceline(cfp);
1309
+ file = iseq->filename;
1310
+
1311
+ //arguments pushed this way: *reg_cfp->sp++ = recv; for (i = 0; i < argc; i++) *reg_cfp->sp++ = argv[i];
1312
+ //local vars = cfp->iseq->local_size - cfp->iseq->arg_size;
1313
+ //in memory: receiver params locals (bp(incremented))
1314
+ VALUE* argv = &cfp->bp[- cfp->iseq->local_size - 1];
1315
+ if ((*iter)(arg, file, line_no, iseq->name, cfp->iseq->arg_size, argv)) break;
1316
+ }
1317
+ } else
1318
+ if (RUBYVM_CFUNC_FRAME_P(cfp)) {
1319
+ ID id = cfp->me->def? cfp->me->def->original_id : cfp->me->called_id;
1320
+
1321
+ if (NIL_P(file)) file = ruby_engine_name;
1322
+
1323
+ if (id != ID_ALLOCATOR){
1324
+ VALUE* argv = NULL;
1325
+ // when argc==-1/-2(variable length params without/with splat) - the cfp has no info on params count :(
1326
+ //TODO: infere from somewhere ex. find self in stack? (not guaranted btw, for example: obj.method(obj, 123, obj) - will find last param instead of self)
1327
+ if(cfp->me->def->body.cfunc.argc >= 0){ //only fixed args
1328
+ argv = &cfp->bp[- cfp->me->def->body.cfunc.argc - 2]; // args+self, bp was incremented thus minus 2
1329
+ }
1330
+ //file+line no from previous iseq frame
1331
+ if((*iter)(arg, file, line_no, rb_id2str(id), cfp->me->def->body.cfunc.argc, argv)) break;
1332
+ }
1333
+ }
1334
+ cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
1334
1335
  }
1335
- cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
1336
- }
1337
- return TRUE;
1336
+ return TRUE;
1338
1337
  }
1339
1338
 
1340
1339
  static void dump_thread(const rb_thread_t* th, walk_ctx_t *ctx){
@@ -1355,6 +1354,7 @@ static void dump_thread(const rb_thread_t* th, walk_ctx_t *ctx){
1355
1354
 
1356
1355
  yg_cstring("cfp");
1357
1356
  yajl_gen_array_open(ctx->yajl);
1357
+ //TODO: this is kind of backtrace, but other direction plus some other info, merge it in backtrace.
1358
1358
  while (cfp != limit_cfp) {
1359
1359
  yajl_gen_map_open(ctx->yajl);
1360
1360
  rb_iseq_t *iseq = cfp->iseq;
@@ -1395,7 +1395,7 @@ static void dump_thread(const rb_thread_t* th, walk_ctx_t *ctx){
1395
1395
 
1396
1396
  yg_cstring("backtrace");
1397
1397
  yg_array();
1398
- vm_backtrace_each(th, -1, NULL, dump_backtrace, ctx);
1398
+ vm_backtrace_each_ext(th, -1, NULL, dump_backtrace, ctx);
1399
1399
  yg_array_end();
1400
1400
 
1401
1401
  //TODO: mark other...
@@ -1472,7 +1472,6 @@ static void dump_machine_context(walk_ctx_t *ctx){
1472
1472
  //mark_locations_array(objspace, save_regs_gc_mark.v, numberof(save_regs_gc_mark.v));
1473
1473
  VALUE* x = save_regs_gc_mark.v;
1474
1474
  unsigned long n = numberof(save_regs_gc_mark.v);
1475
- //printf("registers\n");
1476
1475
  while (n--) {
1477
1476
  VALUE v = *(x++);
1478
1477
  if(is_in_heap((void*)v, NULL))
@@ -1480,7 +1479,6 @@ static void dump_machine_context(walk_ctx_t *ctx){
1480
1479
  }
1481
1480
  yajl_gen_array_close(ctx->yajl);
1482
1481
 
1483
- //printf("stack: %p %p\n", stack_start, stack_end);
1484
1482
  yg_cstring("stack");
1485
1483
  yajl_gen_array_open(ctx->yajl);
1486
1484
  //rb_gc_mark_locations(stack_start, stack_end);
@@ -1489,10 +1487,8 @@ static void dump_machine_context(walk_ctx_t *ctx){
1489
1487
  x = stack_start;
1490
1488
  while (n--) {
1491
1489
  VALUE v = *(x++);
1492
- //printf("val: %p\n", (void*)v);
1493
1490
  //FIXME: other objspace (not default one?)
1494
1491
  if(is_in_heap((void*)v, NULL)) {
1495
- //printf("ON heap\n");
1496
1492
  yg_id(v);
1497
1493
  }
1498
1494
  }
@@ -1501,39 +1497,73 @@ static void dump_machine_context(walk_ctx_t *ctx){
1501
1497
  yajl_gen_array_close(ctx->yajl);
1502
1498
  }
1503
1499
 
1504
- static int dump_iv_entry1(ID key, rb_const_entry_t* ce/*st_data_t val*/, walk_ctx_t *ctx){
1500
+ #ifdef HAVE_RB_CLASS_TBL
1501
+ // 1.9.2, rb_class_tbl fails to be linked in 1.9.3 :(
1502
+
1503
+ static int dump_class_tbl_entry(ID key, rb_const_entry_t* ce/*st_data_t val*/, walk_ctx_t *ctx){
1505
1504
  if (!rb_is_const_id(key)) return ST_CONTINUE; //?
1506
- //rb_const_entry_t* ce = val;
1507
1505
  VALUE value = ce->value;
1508
1506
 
1509
- //printf("cls %p\n", (void*)value);
1510
- //printf("id: %s\n", rb_id2name(key));
1507
+ char* id = rb_id2name(key);
1508
+ if(id)
1509
+ yg_cstring(id);
1510
+ else
1511
+ yg_cstring("(unknown)");
1512
+ yg_id(value);
1513
+ return ST_CONTINUE;
1514
+ }
1515
+ #endif
1511
1516
 
1512
- //val - damaged in some way?
1517
+ #ifdef HAVE_RB_GLOBAL_TBL
1518
+ static int dump_global_tbl_entry(ID key, struct rb_global_entry* ge/*st_data_t val*/, walk_ctx_t *ctx){
1519
+ char* id = rb_id2name(key);
1520
+ if(id)
1521
+ yg_cstring(id);
1522
+ else
1523
+ yg_cstring("(unknown)");
1513
1524
 
1514
- //printf("name %s\n", RSTRING_PTR(rb_class_path(rb_class_real_checked(value))));
1525
+ yg_map();
1515
1526
 
1516
- //if(is_in_heap(value, NULL)){
1517
- //printf("on heap\n");
1518
- yg_id(value);
1519
- //}
1527
+ Dl_info info;
1528
+ if(dladdr(ge->var->getter, &info) && info.dli_sname){
1529
+ yg_cstring("getter");
1530
+ yg_cstring(info.dli_sname);
1520
1531
 
1532
+ if(!strcmp("rb_gvar_val_getter", info.dli_sname)){
1533
+ yg_cstring("data");
1534
+ yg_id(ge->var->data);
1535
+ }
1536
+ }
1537
+
1538
+ yg_cstring("setter");
1539
+ yg_funcaddr(ge->var->setter);
1540
+
1541
+ yg_map_end();
1521
1542
  return ST_CONTINUE;
1522
1543
  }
1544
+ #endif
1523
1545
 
1524
- // static void try_dump_generic_ivars(walk_ctx_t* ctx){
1525
- // //very nasty hack:
1526
- // #if defined(__x86_64__) && defined(__GNUC__) && !defined(__native_client__)
1527
- // printf("Trying generic ivars\n");
1528
- // //TODO: config to turn this off in case this will not work
1529
1546
 
1530
- // VALUE stack_end1, stack_end2;
1531
- // rb_gc_set_stack_end(&stack_end1);
1532
- // st_table* tbl = rb_generic_ivar_table();
1533
- // rb_gc_set_stack_end(&stack_end2);
1547
+ #include <stdarg.h>
1548
+ static bool g_verbose = false;
1549
+ static int log_printf(const char* format, ...){
1550
+ va_list list;
1551
+ va_start(list, format);
1552
+ if(g_verbose)
1553
+ vprintf(format, list);
1554
+ va_end(list);
1555
+ }
1556
+
1557
+ #define log log_printf
1558
+
1559
+ static VALUE heapdump_verbose(VALUE self){
1560
+ return g_verbose ? Qtrue : Qfalse;
1561
+ }
1534
1562
 
1535
- // #endif
1536
- // }
1563
+ static VALUE heapdump_verbose_setter(VALUE self, VALUE verbose){
1564
+ g_verbose = RTEST(verbose);
1565
+ return heapdump_verbose(self);
1566
+ }
1537
1567
 
1538
1568
 
1539
1569
  //public symbol, can be used from GDB
@@ -1544,7 +1574,7 @@ void heapdump_dump(const char* filename){
1544
1574
  if(!filename){
1545
1575
  filename = "dump.json";
1546
1576
  }
1547
- printf("Dump should go to %s\n", filename);
1577
+ log("Dump should go to %s\n", filename);
1548
1578
  ctx->file = fopen(filename, "wt");
1549
1579
  ctx->yajl = yajl_gen_alloc(NULL,NULL);
1550
1580
  yajl_gen_array_open(ctx->yajl);
@@ -1553,7 +1583,7 @@ void heapdump_dump(const char* filename){
1553
1583
  yajl_gen_map_open(ctx->yajl);
1554
1584
  ygh_cstring("id", "_ROOTS_");
1555
1585
 
1556
- printf("machine context\n");
1586
+ log("machine context\n");
1557
1587
 
1558
1588
  dump_machine_context(ctx);
1559
1589
  flush_yajl(ctx);
@@ -1561,24 +1591,39 @@ void heapdump_dump(const char* filename){
1561
1591
 
1562
1592
  struct gc_list *list;
1563
1593
  /* mark protected global variables */
1564
- printf("global_List\n");
1594
+ log("global_list\n");
1595
+ yg_cstring("globals");
1596
+ yg_array();
1565
1597
  for (list = GET_THREAD()->vm->global_List; list; list = list->next) {
1566
1598
  VALUE v = *list->varptr;
1567
- //printf("global %p\n", v);
1599
+ yg_id(v);
1568
1600
  }
1601
+ yg_array_end();
1569
1602
 
1570
- #ifdef HAVE_RB_CLASS_TBL
1571
- yg_cstring("classes");
1572
- yajl_gen_array_open(ctx->yajl);
1573
- printf("classes\n");
1574
- if (rb_class_tbl && rb_class_tbl->num_entries > 0)
1575
- st_foreach(rb_class_tbl, dump_iv_entry1, (st_data_t)ctx);
1576
- else printf("no classes\n");
1577
- yajl_gen_array_close(ctx->yajl);
1578
- flush_yajl(ctx);
1603
+ //TODO: rb_global_tbl
1604
+ #ifdef HAVE_RB_GLOBAL_TBL
1605
+ st_table *rb_global_tbl = rb_get_global_tbl();
1606
+ if (rb_global_tbl && rb_global_tbl->num_entries > 0){
1607
+ log("globals\n");
1608
+ yg_cstring("global_tbl");
1609
+ yg_map();
1610
+ st_foreach(rb_global_tbl, dump_global_tbl_entry, (st_data_t)ctx);
1611
+ yg_map_end();
1612
+ flush_yajl(ctx);
1613
+ }
1579
1614
  #endif
1580
1615
 
1581
- //try_dump_generic_ivars();
1616
+ #ifdef HAVE_RB_CLASS_TBL
1617
+ st_table *rb_class_tbl = rb_get_class_tbl();
1618
+ if (rb_class_tbl && rb_class_tbl->num_entries > 0){
1619
+ log("classes\n");
1620
+ yg_cstring("classes");
1621
+ yg_map();
1622
+ st_foreach(rb_class_tbl, dump_class_tbl_entry, (st_data_t)ctx);
1623
+ yg_map_end();
1624
+ flush_yajl(ctx);
1625
+ }
1626
+ #endif
1582
1627
 
1583
1628
  //TODO: other gc entry points - symbols, encodings, etc.
1584
1629
 
@@ -1587,7 +1632,7 @@ void heapdump_dump(const char* filename){
1587
1632
  fprintf(ctx->file, "\n");
1588
1633
 
1589
1634
  //now dump all live objects
1590
- printf("starting objspace walk\n");
1635
+ log("starting objspace walk\n");
1591
1636
  rb_objspace_each_objects(objspace_walker, ctx);
1592
1637
 
1593
1638
  yajl_gen_array_close(ctx->yajl);
@@ -1595,7 +1640,7 @@ void heapdump_dump(const char* filename){
1595
1640
  yajl_gen_free(ctx->yajl);
1596
1641
  fclose(ctx->file);
1597
1642
 
1598
- printf("Walker called %d times, seen %d live objects.\n", ctx->walker_called, ctx->live_objects);
1643
+ log("Walker called %d times, seen %d live objects.\n", ctx->walker_called, ctx->live_objects);
1599
1644
  }
1600
1645
 
1601
1646
  static VALUE
@@ -1606,6 +1651,113 @@ rb_heapdump_dump(VALUE self, VALUE filename)
1606
1651
  return Qnil;
1607
1652
  }
1608
1653
 
1654
+
1655
+
1656
+ // HeapDump.count_objects:
1657
+
1658
+ #undef YAJL
1659
+ #define YAJL yajl
1660
+ static int
1661
+ iterate_user_type_counts(VALUE key, VALUE value, yajl_gen yajl){
1662
+ yg_rstring(key);
1663
+ yg_int(FIX2LONG(value));
1664
+ return ST_CONTINUE;
1665
+ }
1666
+
1667
+ static VALUE
1668
+ rb_heapdump_count_objects(VALUE self, VALUE string_prefixes, VALUE do_gc){
1669
+ rb_check_array_type(string_prefixes);
1670
+ yajl_gen_config cfg;
1671
+ memset(&cfg, 0, sizeof(cfg));
1672
+ cfg.beautify = true;
1673
+ cfg.htmlSafe = true;
1674
+ cfg.indentString = " ";
1675
+ yajl_gen yajl = yajl_gen_alloc(&cfg,NULL);
1676
+ yg_map();
1677
+ if(do_gc){
1678
+ yg_cstring("gc_ran");
1679
+ yg_bool(true);
1680
+ rb_gc_start();
1681
+ }
1682
+
1683
+ rb_objspace_t *objspace = GET_THREAD()->vm->objspace;
1684
+ size_t counts[T_MASK+1];
1685
+ size_t freed = 0;
1686
+ size_t total = 0;
1687
+ size_t i;
1688
+ VALUE hash = rb_hash_new();
1689
+
1690
+ for (i = 0; i <= T_MASK; i++) counts[i] = 0;
1691
+
1692
+ FOR_EACH_HEAP_SLOT(p)
1693
+ // danger: allocates memory while walking heap
1694
+ if (p->as.basic.flags) {
1695
+ int type = BUILTIN_TYPE(p);
1696
+ counts[type]++;
1697
+ if(type == T_OBJECT){
1698
+ //take class etc.
1699
+ VALUE cls = rb_class_real_checked(CLASS_OF(p));
1700
+ if(!cls) continue;
1701
+ VALUE class_name = rb_class_path(cls);
1702
+ long int n = RARRAY_LEN(string_prefixes)-1;
1703
+ for(; n >= 0; n--){
1704
+ VALUE prefix = rb_check_string_type(RARRAY_PTR(string_prefixes)[n]);
1705
+ if(NIL_P(prefix)) continue;
1706
+ rb_enc_check(class_name, prefix);
1707
+ if (RSTRING_LEN(class_name) < RSTRING_LEN(prefix)) continue;
1708
+ if (!memcmp(RSTRING_PTR(class_name), RSTRING_PTR(prefix), RSTRING_LEN(prefix)))
1709
+ if(RSTRING_LEN(class_name) == RSTRING_LEN(prefix) ||
1710
+ RSTRING_PTR(class_name)[RSTRING_LEN(prefix)] == ':'){
1711
+ //class match
1712
+ VALUE val = rb_hash_aref(hash, class_name);
1713
+ long num;
1714
+ if(FIXNUM_P(val)){
1715
+ num = FIX2LONG(val) + 1;
1716
+ } else {
1717
+ num = 1;
1718
+ }
1719
+ rb_hash_aset(hash, class_name, LONG2FIX(num));
1720
+ }
1721
+ }
1722
+ }
1723
+ } else {
1724
+ freed++;
1725
+ }
1726
+ FOR_EACH_HEAP_SLOT_END(total)
1727
+
1728
+ ygh_int("total_slots", total);
1729
+ ygh_int("free_slots", freed);
1730
+ yg_cstring("basic_types");
1731
+ yg_map();
1732
+ for (i = 0; i <= T_MASK; i++) {
1733
+ if(!counts[i]) continue;
1734
+ yg_cstring(rb_type_str((int)i));
1735
+ yg_int(counts[i]);
1736
+ }
1737
+ yg_map_end();
1738
+
1739
+ yg_cstring("user_types");
1740
+ yg_map();
1741
+ rb_hash_foreach(hash, iterate_user_type_counts, (VALUE)yajl);
1742
+ yg_map_end();
1743
+
1744
+ yg_map_end(); //all document
1745
+
1746
+ //flush yajl:
1747
+ const unsigned char* buf;
1748
+ unsigned int len;
1749
+ if(yajl_gen_get_buf(yajl, &buf, &len) == yajl_gen_status_ok){
1750
+ //fwrite(buf, len, 1, ctx->file);
1751
+ VALUE res = rb_str_new(buf, len);
1752
+ yajl_gen_clear(yajl);
1753
+ yajl_gen_free(yajl);
1754
+ return res;
1755
+ } else {
1756
+ return Qnil;
1757
+ }
1758
+ #undef YAJL
1759
+ }
1760
+
1609
1761
  void Init_heap_dump(){
1610
1762
  //ruby-internal need to be required before linking us, but just in case..
1611
1763
  ID require, gem;
@@ -1619,4 +1771,9 @@ void Init_heap_dump(){
1619
1771
 
1620
1772
  rb_mHeapDumpModule = rb_define_module("HeapDump");
1621
1773
  rb_define_singleton_method(rb_mHeapDumpModule, "dump_ext", rb_heapdump_dump, 1);
1774
+ rb_define_singleton_method(rb_mHeapDumpModule, "count_objects_ext", rb_heapdump_count_objects, 2);
1775
+
1776
+ rb_define_singleton_method(rb_mHeapDumpModule, "verbose", heapdump_verbose, 0);
1777
+ rb_define_singleton_method(rb_mHeapDumpModule, "verbose=", heapdump_verbose_setter, 1);
1778
+
1622
1779
  }
@@ -67,11 +67,7 @@ struct heaps_slot {
67
67
  void *membase;
68
68
  RVALUE *slot;
69
69
  size_t limit;
70
- uintptr_t *bits;
71
- RVALUE *freelist;
72
- struct heaps_slot *next;
73
- struct heaps_slot *prev;
74
- struct heaps_slot *free_next;
70
+ int finalize_flag;
75
71
  };
76
72
 
77
73
  struct heaps_header {
@@ -79,16 +75,6 @@ struct heaps_header {
79
75
  uintptr_t *bits;
80
76
  };
81
77
 
82
- struct sorted_heaps_slot {
83
- RVALUE *start;
84
- RVALUE *end;
85
- struct heaps_slot *slot;
86
- };
87
-
88
- struct heaps_free_bitmap {
89
- struct heaps_free_bitmap *next;
90
- };
91
-
92
78
  struct gc_list {
93
79
  VALUE *varptr;
94
80
  struct gc_list *next;
@@ -189,4 +175,10 @@ is_pointer_to_heap(rb_objspace_t *objspace, void *ptr)
189
175
  }
190
176
  }
191
177
  return FALSE;
192
- }
178
+ }
179
+
180
+ #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) {\
181
+ RVALUE *p = heaps[i].slot; RVALUE *pend = p + heaps[i].limit;\
182
+ if(!p || p < heaps[i].membase) continue;\
183
+ for (; p < pend; p++) {
184
+ #define FOR_EACH_HEAP_SLOT_END(total) } total += heaps[i].limit; }
@@ -39,7 +39,39 @@ struct METHOD {
39
39
  #define METHOD_DEFINITIONP(m) m->me.def
40
40
 
41
41
  #define HAVE_RB_CLASS_TBL 1
42
- //class.c:
43
- extern st_table *rb_class_tbl;
42
+
43
+ inline st_table * rb_get_class_tbl(){
44
+ //class.c:
45
+ extern st_table *rb_class_tbl;
46
+ return rb_class_tbl;
47
+ }
48
+
49
+ #define HAVE_RB_GLOBAL_TBL 1
50
+ inline st_table * rb_get_global_tbl(){
51
+ //class.c:
52
+ extern st_table *rb_global_tbl;
53
+ return rb_global_tbl;
54
+ }
55
+ #define gvar_getter_t rb_gvar_getter_t
56
+ #define gvar_setter_t rb_gvar_setter_t
57
+ #define gvar_marker_t rb_gvar_marker_t
58
+
59
+ struct trace_var {
60
+ int removed;
61
+ void (*func)(VALUE arg, VALUE val);
62
+ VALUE data;
63
+ struct trace_var *next;
64
+ };
65
+
66
+ //struct global_variable {
67
+ struct rb_global_variable {
68
+ int counter;
69
+ void *data;
70
+ gvar_getter_t *getter;
71
+ gvar_setter_t *setter;
72
+ gvar_marker_t *marker;
73
+ int block_trace;
74
+ struct trace_var *trace;
75
+ };
44
76
 
45
77
  extern VALUE ruby_engine_name;
@@ -189,3 +189,10 @@ is_pointer_to_heap(rb_objspace_t *objspace, void *ptr)
189
189
  }
190
190
  return FALSE;
191
191
  }
192
+
193
+ #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) {\
194
+ RVALUE *p = objspace->heap.sorted[i].start, *pend = objspace->heap.sorted[i].end;\
195
+ if(!p) continue;\
196
+ for (; p < pend; p++) {
197
+ #define FOR_EACH_HEAP_SLOT_END(total) } total += objspace->heap.sorted[i].slot->limit; }
198
+
@@ -41,9 +41,39 @@ struct METHOD {
41
41
  #define METHOD_DEFINITIONP(m) (m->me ? m->me->def : NULL)
42
42
 
43
43
  //class.c:
44
- // #define HAVE_RB_CLASS_TBL 1
45
- //For som reason this fails to link on 1.9.3 :(
46
- // extern st_table *rb_class_tbl;
44
+ #define HAVE_RB_CLASS_TBL 1
45
+ //For som reason this fails to link directly on 1.9.3 :(
46
+
47
+ //HACK:
48
+ // otool -L `which ruby`
49
+ // /Users/vasfed/.rvm/rubies/ruby-1.9.3-p194/bin/ruby:
50
+ // @executable_path/../lib/libruby.1.9.1.dylib (compatibility version 1.9.1, current version 1.9.1)
51
+ // nm ~/.rvm/rubies/ruby-1.9.3-p194/lib/libruby.1.9.1.dylib | grep rb_class_tbl
52
+ // 000000000024be28 s _rb_class_tbl
53
+ // 00000000000b311c T _rb_intern
54
+ // 000000000024bd38 S _rb_mKernel
55
+
56
+ #include <dlfcn.h>
57
+
58
+ inline st_table * rb_get_class_tbl(){
59
+ Dl_info info;
60
+ if(!dladdr(rb_intern, &info) || !info.dli_fname){
61
+ return NULL;
62
+ }
63
+ void* image = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL);
64
+ // printf("Image is %p, addr is %p (%p rel)\n", image, rb_intern, ((void*)rb_intern - image));
65
+ if(image)
66
+ {
67
+ void* tbl = dlsym(image, "_rb_class_tbl");
68
+ dlclose(image);
69
+ if(tbl)
70
+ return tbl;
71
+ }
72
+
73
+ //TODO: parse sym table and calculate address?
74
+
75
+ return NULL;
76
+ }
47
77
 
48
78
  #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
49
79
 
@@ -1,3 +1,3 @@
1
1
  module HeapDump
2
- VERSION = "0.0.28"
2
+ VERSION = "0.0.29"
3
3
  end
data/lib/heap_dump.rb CHANGED
@@ -9,4 +9,18 @@ module HeapDump
9
9
  GC.start if gc_before_dump
10
10
  return dump_ext(filename)
11
11
  end
12
+
13
+ # provides an object count - like ObjectSpace.count_objects, but also for user classes
14
+ def self.count_objects namespaces_array, gc=false
15
+ unless namespaces_array.is_a?(Array) && namespaces_array.all?{|v|v.is_a? Symbol}
16
+ if namespaces_array.is_a? Symbol
17
+ namespaces_array = [namespaces_array]
18
+ else
19
+ #TODO: actually, better way is to accept anything convertable, even module itself
20
+ raise ArgumentError.new("namespaces_array must be a symbol or array of symbols")
21
+ end
22
+ end
23
+ prefixes_array = namespaces_array.map{|c| c.to_s}
24
+ return count_objects_ext(prefixes_array, !!gc)
25
+ end
12
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heap_dump
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.28
4
+ version: 0.0.29
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-12 00:00:00.000000000 Z
12
+ date: 2012-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debugger-ruby_core_source
@@ -105,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  version: '0'
106
106
  segments:
107
107
  - 0
108
- hash: -3488312299511292318
108
+ hash: -3423213544880593547
109
109
  requirements: []
110
110
  rubyforge_project:
111
111
  rubygems_version: 1.8.24