heap_dump 0.0.28 → 0.0.29
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +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
|