heap_dump 0.0.28 → 0.0.29

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