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 +80 -28
- data/Rakefile +4 -1
- data/ext/heap_dump/heap_dump.c +319 -162
- data/ext/heap_dump/specific/ruby-1.9.2/gc_internal.h +8 -16
- data/ext/heap_dump/specific/ruby-1.9.2/internal_typed_data.h +34 -2
- data/ext/heap_dump/specific/ruby-1.9.3/gc_internal.h +7 -0
- data/ext/heap_dump/specific/ruby-1.9.3/internal_typed_data.h +33 -3
- data/lib/heap_dump/version.rb +1 -1
- data/lib/heap_dump.rb +14 -0
- metadata +3 -3
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
|
-
|
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
|
data/ext/heap_dump/heap_dump.c
CHANGED
@@ -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
|
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(
|
71
|
-
#define yg_double(d) (yajl_gen_double(
|
72
|
-
#define yg_null() yajl_gen_null(
|
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(
|
86
|
-
#define yg_map_end() yajl_gen_map_close(
|
87
|
-
#define yg_array() yajl_gen_array_open(
|
88
|
-
#define yg_array_end() yajl_gen_array_close(
|
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
|
-
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
|
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, "%
|
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
|
-
//
|
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
|
-
|
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",
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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",
|
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
|
-
|
1295
|
-
|
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
|
-
|
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
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
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
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1510
|
-
|
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
|
-
|
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
|
-
|
1525
|
+
yg_map();
|
1515
1526
|
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
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
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1599
|
+
yg_id(v);
|
1568
1600
|
}
|
1601
|
+
yg_array_end();
|
1569
1602
|
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
45
|
-
//For som reason this fails to link on 1.9.3 :(
|
46
|
-
|
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
|
|
data/lib/heap_dump/version.rb
CHANGED
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.
|
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
|
+
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: -
|
108
|
+
hash: -3423213544880593547
|
109
109
|
requirements: []
|
110
110
|
rubyforge_project:
|
111
111
|
rubygems_version: 1.8.24
|