memprof 0.3.6 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +284 -0
- data/ext/elf.c +60 -26
- data/ext/extconf.rb +1 -1
- data/ext/memprof.c +40 -30
- data/ext/tracer.h +2 -0
- data/ext/tracers/postgres.c +73 -0
- data/ext/tracers/resources.c +89 -0
- data/memprof.gemspec +1 -2
- metadata +9 -7
- data/README.rdoc +0 -118
data/README.md
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
# memprof
|
2
|
+
(c) Joe Damato
|
3
|
+
@joedamato
|
4
|
+
http://timetobleed.com
|
5
|
+
|
6
|
+
Memprof is a Ruby level memory profiler that can help you find reference
|
7
|
+
leaks in your application.
|
8
|
+
|
9
|
+
Memprof can also do very lightweight function call tracing to figure out
|
10
|
+
which system and library calls are happening in your code.
|
11
|
+
|
12
|
+
# Installation
|
13
|
+
|
14
|
+
gem install memprof
|
15
|
+
|
16
|
+
# API
|
17
|
+
|
18
|
+
## Memprof.stats
|
19
|
+
|
20
|
+
Memprof.start
|
21
|
+
12.times{ "abc" }
|
22
|
+
Memprof.stats
|
23
|
+
Memprof.stop
|
24
|
+
|
25
|
+
Start tracking file/line information for objects created after calling
|
26
|
+
`Memprof.start`, and print out a summary of file:line/class pairs
|
27
|
+
created.
|
28
|
+
|
29
|
+
12 file.rb:2:String
|
30
|
+
|
31
|
+
*Note*: Call `Memprof.stats` again after `GC.start` to see which objects
|
32
|
+
are cleaned up by the garbage collector:
|
33
|
+
|
34
|
+
Memprof.start
|
35
|
+
10.times{ $last_str = "abc" }
|
36
|
+
|
37
|
+
puts '=== Before GC'
|
38
|
+
Memprof.stats
|
39
|
+
|
40
|
+
puts '=== After GC'
|
41
|
+
GC.start
|
42
|
+
Memprof.stats
|
43
|
+
|
44
|
+
Memprof.stop
|
45
|
+
|
46
|
+
After `GC.start`, only the very last instance of `"abc"` will still
|
47
|
+
exist:
|
48
|
+
|
49
|
+
=== Before GC
|
50
|
+
10 file.rb:2:String
|
51
|
+
=== After GC
|
52
|
+
1 file.rb:2:String
|
53
|
+
|
54
|
+
*Note*: Use `Memprof.stats("/path/to/file")` to write results to a file.
|
55
|
+
|
56
|
+
*Note*: Use `Memprof.stats!` to clear out tracking data after printing
|
57
|
+
out results.
|
58
|
+
|
59
|
+
## Memprof.track
|
60
|
+
|
61
|
+
Simple wrapper for `Memprof.stats` that will start/stop memprof around a
|
62
|
+
given block of ruby code.
|
63
|
+
|
64
|
+
Memprof.track{
|
65
|
+
100.times{ "abc" }
|
66
|
+
100.times{ 1.23 + 1 }
|
67
|
+
100.times{ Module.new }
|
68
|
+
}
|
69
|
+
|
70
|
+
For the block of ruby code, print out file:line/class pairs for
|
71
|
+
ruby objects created.
|
72
|
+
|
73
|
+
100 file.rb:2:String
|
74
|
+
100 file.rb:3:Float
|
75
|
+
100 file.rb:4:Module
|
76
|
+
|
77
|
+
*Note*: You can call GC.start at the end of the block to print out only
|
78
|
+
objects that are 'leaking' (i.e. objects that still have inbound
|
79
|
+
references).
|
80
|
+
|
81
|
+
*Note*: Use `Memprof.track("/path/to/file")` to write the results to a
|
82
|
+
file instead of stdout.
|
83
|
+
|
84
|
+
## Memprof.dump
|
85
|
+
|
86
|
+
Memprof.dump{
|
87
|
+
"hello" + "world"
|
88
|
+
}
|
89
|
+
|
90
|
+
Dump out all objects created in a given ruby block as detailed json
|
91
|
+
objects.
|
92
|
+
|
93
|
+
{
|
94
|
+
"_id": "0x15e5018",
|
95
|
+
|
96
|
+
"file": "file.rb",
|
97
|
+
"line": 2,
|
98
|
+
|
99
|
+
"type": "string",
|
100
|
+
"class_name": "String",
|
101
|
+
|
102
|
+
"length": 10,
|
103
|
+
"data": "helloworld"
|
104
|
+
}
|
105
|
+
|
106
|
+
*Note*: Use `Memprof.dump("/path/to/filename")` to write the json output
|
107
|
+
to a file, one per line.
|
108
|
+
|
109
|
+
## Memprof.dump_all
|
110
|
+
|
111
|
+
Memprof.dump_all("myapp_heap.json")
|
112
|
+
|
113
|
+
Dump out all live objects inside the Ruby VM to `myapp_heap.json`, one
|
114
|
+
per line.
|
115
|
+
|
116
|
+
### [memprof.com](http://memprof.com) heap visualizer
|
117
|
+
|
118
|
+
# load memprof before requiring rubygems, so objects created by
|
119
|
+
# rubygems itself are tracked by memprof too
|
120
|
+
require `gem which memprof/signal`.strip
|
121
|
+
|
122
|
+
require 'rubygems'
|
123
|
+
require 'myapp'
|
124
|
+
|
125
|
+
Installs a `URG` signal handler and starts tracking file/line
|
126
|
+
information for newly created ruby objects. When the process receives
|
127
|
+
`SIGURG`, it will fork and call `Memprof.dump_all` to write out the
|
128
|
+
entire heap to a json file.
|
129
|
+
|
130
|
+
Use the `memprof` command to send the signal and upload the heap to
|
131
|
+
[memprof.com](http://memprof.com):
|
132
|
+
|
133
|
+
memprof --pid <PID> --name my_leaky_app --key <API_KEY>
|
134
|
+
|
135
|
+
## Memprof.trace
|
136
|
+
|
137
|
+
require 'open-uri'
|
138
|
+
require 'mysql'
|
139
|
+
require 'memcached'
|
140
|
+
|
141
|
+
Memprof.trace{
|
142
|
+
10.times{ Module.new }
|
143
|
+
10.times{ GC.start }
|
144
|
+
10.times{ open('http://google.com/') }
|
145
|
+
10.times{ Mysql.connect.query("select 1+2") }
|
146
|
+
10.times{ Memcached.new.get('memprof') }
|
147
|
+
}
|
148
|
+
|
149
|
+
For a given block of ruby code, count:
|
150
|
+
|
151
|
+
- number of objects created per type
|
152
|
+
- number of calls to and time spent in GC
|
153
|
+
- number of calls to and time spent in connect/read/write/select
|
154
|
+
- number of calls to and time spent in mysql queries
|
155
|
+
- number of calls to and responses to memcached commands
|
156
|
+
- number of calls to and bytes through malloc/realloc/free
|
157
|
+
|
158
|
+
The resulting json report looks like:
|
159
|
+
|
160
|
+
{
|
161
|
+
"objects": {
|
162
|
+
"created": 10,
|
163
|
+
"types": {
|
164
|
+
"module": 10, # Module.new
|
165
|
+
}
|
166
|
+
},
|
167
|
+
|
168
|
+
"gc": {
|
169
|
+
"calls": 10, # GC.start
|
170
|
+
"time": 0.17198
|
171
|
+
},
|
172
|
+
|
173
|
+
"fd": {
|
174
|
+
"connect": {
|
175
|
+
"calls": 10, # open('http://google.com')
|
176
|
+
"time": 0.0110
|
177
|
+
}
|
178
|
+
},
|
179
|
+
|
180
|
+
"mysql": {
|
181
|
+
"queries": 10, # Mysql.connect.query("select 1+2")
|
182
|
+
"time": 0.0006
|
183
|
+
},
|
184
|
+
|
185
|
+
"memcache": {
|
186
|
+
"get": {
|
187
|
+
"calls": 10, # Memcached.new.get('memprof')
|
188
|
+
"responses": {
|
189
|
+
"notfound": 10
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
195
|
+
*Note*: To write json to a file instead, set `Memprof.trace_filename =
|
196
|
+
"/path/to/file.json"`
|
197
|
+
|
198
|
+
## Memprof.trace_request
|
199
|
+
|
200
|
+
Memprof.trace_request(env){ @app.call(env) }
|
201
|
+
|
202
|
+
Like `Memprof.trace`, but assume an incoming Rack request and include
|
203
|
+
information about the request itself.
|
204
|
+
|
205
|
+
{
|
206
|
+
"start" : 1272424769750716,
|
207
|
+
"tracers" : {
|
208
|
+
/* ... */
|
209
|
+
},
|
210
|
+
"rails" : {
|
211
|
+
"controller" : "home",
|
212
|
+
"action" : "index"
|
213
|
+
},
|
214
|
+
"request" : {
|
215
|
+
"REQUEST_URI" : "/home",
|
216
|
+
"REQUEST_METHOD" : "GET",
|
217
|
+
"REMOTE_ADDR" : "127.0.0.1",
|
218
|
+
"QUERY_STRING" : null
|
219
|
+
},
|
220
|
+
"time" : 1.3442
|
221
|
+
}
|
222
|
+
|
223
|
+
# Middlewares
|
224
|
+
|
225
|
+
## Memprof::Middleware
|
226
|
+
|
227
|
+
require 'memprof/middleware'
|
228
|
+
config.middlewares.use(Memprof::Middleware)
|
229
|
+
|
230
|
+
Wrap each request in a `Memprof.track` to print out all object
|
231
|
+
location/type pairs created during that request.
|
232
|
+
|
233
|
+
*Note*: It is preferable to run this in staging or production mode with
|
234
|
+
Rails applications, since development mode creates a lot of unnecessary
|
235
|
+
objects during each request.
|
236
|
+
|
237
|
+
*Note*: To force a GC run before printing out a report, pass in
|
238
|
+
`:force_gc => true` to the middleware.
|
239
|
+
|
240
|
+
## Memprof::Tracer
|
241
|
+
|
242
|
+
require 'memprof/tracer'
|
243
|
+
config.middleware.insert(0, Memprof::Tracer)
|
244
|
+
|
245
|
+
Wrap each request in a `Memprof.trace_request` and write results to
|
246
|
+
`/tmp/memprof_tracer-PID.json`
|
247
|
+
|
248
|
+
## Memprof::Filter
|
249
|
+
|
250
|
+
Similar to `Memprof::Tracer`, but for legacy Rails 2.2 applications.
|
251
|
+
|
252
|
+
class ApplicationController < ActionController::Base
|
253
|
+
require 'memprof/tracer'
|
254
|
+
around_filter(Memprof::Filter)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Compatibility
|
258
|
+
|
259
|
+
Memprof supports all 1.8.x (MRI and REE) VMs, as long as they are 64-bit
|
260
|
+
and contain debugging symbols. For best results, use RVM to compile ruby
|
261
|
+
and make sure you are on a 64-bit machine.
|
262
|
+
|
263
|
+
The following ruby builds are not supported:
|
264
|
+
|
265
|
+
- Ruby on small/medium EC2 instances (32-bit machines)
|
266
|
+
- OSX's default system ruby (no debugging symbols, fat 32/64-bit
|
267
|
+
binary)
|
268
|
+
|
269
|
+
*Note*: Many linux distributions do not package debugging symbols by
|
270
|
+
default. You can usually install these separately, for example using
|
271
|
+
`apt-get install libruby1.8-dbg`
|
272
|
+
|
273
|
+
## Coming soon
|
274
|
+
|
275
|
+
- support for Ruby 1.9
|
276
|
+
- support for i386/i686 ruby builds
|
277
|
+
|
278
|
+
# Credits
|
279
|
+
|
280
|
+
- Jake Douglas for the Mach-O Snow Leopard support
|
281
|
+
- Aman Gupta for various bug fixes and other cleanup
|
282
|
+
- Rob Benson for initial 1.9 support and cleanup
|
283
|
+
- Paul Barry for force_gc support in `Memprof::Middleware`
|
284
|
+
|
data/ext/elf.c
CHANGED
@@ -51,6 +51,8 @@ struct elf_info {
|
|
51
51
|
void *text_segment;
|
52
52
|
size_t text_segment_len;
|
53
53
|
|
54
|
+
GElf_Addr got_addr;
|
55
|
+
|
54
56
|
GElf_Addr relplt_addr;
|
55
57
|
Elf_Data *relplt;
|
56
58
|
size_t relplt_count;
|
@@ -129,12 +131,28 @@ struct plt_entry {
|
|
129
131
|
* the entry uses.
|
130
132
|
*/
|
131
133
|
static void *
|
132
|
-
get_got_addr(struct plt_entry *plt)
|
134
|
+
get_got_addr(struct plt_entry *plt, struct elf_info *info)
|
133
135
|
{
|
136
|
+
void *addr = NULL;
|
137
|
+
|
134
138
|
assert(plt != NULL);
|
135
|
-
|
136
|
-
|
137
|
-
|
139
|
+
assert(plt->jmp[0] == 0xff);
|
140
|
+
|
141
|
+
if (plt->jmp[1] == 0x25) {
|
142
|
+
#if defined(_ARCH_x86_64_)
|
143
|
+
// jmpq *0x2ccf3a(%rip)
|
144
|
+
addr = (void *)&(plt->pad) + plt->jmp_disp;
|
145
|
+
#else
|
146
|
+
// jmp *0x81060f0
|
147
|
+
addr = (void *)(plt->jmp_disp);
|
148
|
+
#endif
|
149
|
+
} else if (plt->jmp[1] == 0xa3) {
|
150
|
+
// jmp *0x130(%ebx)
|
151
|
+
addr = (void *)(info->base_addr + info->got_addr + plt->jmp_disp);
|
152
|
+
}
|
153
|
+
|
154
|
+
dbg_printf("PLT addr: %p, .got.plt slot: %p\n", plt, addr);
|
155
|
+
return addr;
|
138
156
|
}
|
139
157
|
|
140
158
|
/*
|
@@ -144,15 +162,15 @@ get_got_addr(struct plt_entry *plt)
|
|
144
162
|
* returns the original function address
|
145
163
|
*/
|
146
164
|
static void *
|
147
|
-
overwrite_got(void *plt, const void *tramp)
|
165
|
+
overwrite_got(void *plt, const void *tramp, struct elf_info *info)
|
148
166
|
{
|
149
167
|
assert(plt != NULL);
|
150
168
|
assert(tramp != NULL);
|
151
169
|
void *ret = NULL;
|
152
170
|
|
153
|
-
memcpy(&ret, get_got_addr(plt), sizeof(void *));
|
154
|
-
copy_instructions(get_got_addr(plt), &tramp, sizeof(void *));
|
155
|
-
dbg_printf("GOT value overwritten to: %p\n", tramp);
|
171
|
+
memcpy(&ret, get_got_addr(plt, info), sizeof(void *));
|
172
|
+
copy_instructions(get_got_addr(plt, info), &tramp, sizeof(void *));
|
173
|
+
dbg_printf("GOT value overwritten to: %p, from: %p\n", tramp, ret);
|
156
174
|
return ret;
|
157
175
|
}
|
158
176
|
|
@@ -237,7 +255,7 @@ hook_required_objects(struct elf_info *info, void *data)
|
|
237
255
|
|
238
256
|
if ((trampee_addr = find_plt_addr(hook_data->sym, info)) != NULL) {
|
239
257
|
dbg_printf("found: %s @ %p\n", hook_data->sym, trampee_addr);
|
240
|
-
overwrite_got(trampee_addr, hook_data->addr);
|
258
|
+
overwrite_got(trampee_addr, hook_data->addr, info);
|
241
259
|
}
|
242
260
|
|
243
261
|
return;
|
@@ -282,6 +300,9 @@ do_bin_allocate_page(struct elf_info *info)
|
|
282
300
|
* grab a page in the lower 4gb of the address space.
|
283
301
|
*/
|
284
302
|
assert((size_t)info->text_segment <= UINT_MAX);
|
303
|
+
#ifndef MAP_32BIT
|
304
|
+
#define MAP_32BIT 0 // no MAP_32BIT defined on certain 32bit systems
|
305
|
+
#endif
|
285
306
|
return mmap(NULL, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);
|
286
307
|
}
|
287
308
|
|
@@ -344,29 +365,39 @@ find_plt_addr(const char *symname, struct elf_info *info)
|
|
344
365
|
|
345
366
|
/* Search through each of the .rela.plt entries */
|
346
367
|
for (i = 0; i < info->relplt_count; i++) {
|
368
|
+
GElf_Rel rel;
|
347
369
|
GElf_Rela rela;
|
348
370
|
GElf_Sym sym;
|
349
371
|
GElf_Addr addr;
|
350
|
-
void *ret;
|
372
|
+
void *ret = NULL;
|
351
373
|
const char *name;
|
352
374
|
|
353
375
|
if (info->relplt->d_type == ELF_T_RELA) {
|
354
376
|
ret = gelf_getrela(info->relplt, i, &rela);
|
355
|
-
|
356
377
|
if (ret == NULL
|
357
378
|
|| ELF64_R_SYM(rela.r_info) >= info->dynsym_count
|
358
379
|
|| gelf_getsym(info->dynsym, ELF64_R_SYM(rela.r_info), &sym) == NULL)
|
359
|
-
|
380
|
+
continue;
|
360
381
|
|
361
|
-
|
382
|
+
} else if (info->relplt->d_type == ELF_T_REL) {
|
383
|
+
ret = gelf_getrel(info->relplt, i, &rel);
|
384
|
+
if (ret == NULL
|
385
|
+
|| ELF64_R_SYM(rel.r_info) >= info->dynsym_count
|
386
|
+
|| gelf_getsym(info->dynsym, ELF64_R_SYM(rel.r_info), &sym) == NULL)
|
387
|
+
continue;
|
388
|
+
} else {
|
389
|
+
dbg_printf("unknown relplt entry type: %d\n", info->relplt->d_type);
|
390
|
+
continue;
|
391
|
+
}
|
362
392
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
393
|
+
name = info->dynstr + sym.st_name;
|
394
|
+
|
395
|
+
/* The name matches the name of the symbol passed in, so get the PLT entry
|
396
|
+
* address and return it.
|
397
|
+
*/
|
398
|
+
if (strcmp(symname, name) == 0) {
|
399
|
+
addr = get_plt_addr(info, i);
|
400
|
+
return (void *)addr;
|
370
401
|
}
|
371
402
|
}
|
372
403
|
|
@@ -426,6 +457,7 @@ do_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)
|
|
426
457
|
for (; esym < lastsym; esym++){
|
427
458
|
/* ignore numeric/empty symbols */
|
428
459
|
if ((esym->st_value == 0) ||
|
460
|
+
(elf->dynstr == 0) ||
|
429
461
|
(ELF32_ST_BIND(esym->st_info)== STB_NUM))
|
430
462
|
continue;
|
431
463
|
|
@@ -563,7 +595,7 @@ bin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig
|
|
563
595
|
if (trampee_addr) {
|
564
596
|
void *ret = NULL;
|
565
597
|
dbg_printf("Found %s in the PLT, inserting tramp...\n", trampee);
|
566
|
-
ret = overwrite_got(trampee_addr, tramp->addr);
|
598
|
+
ret = overwrite_got(trampee_addr, tramp->addr, ruby_info);
|
567
599
|
|
568
600
|
assert(ret != NULL);
|
569
601
|
|
@@ -1079,6 +1111,9 @@ dissect_elf(struct elf_info *info, int find_debug)
|
|
1079
1111
|
if (dyn.d_tag == DT_JMPREL) {
|
1080
1112
|
info->relplt_addr = dyn.d_un.d_ptr;
|
1081
1113
|
}
|
1114
|
+
else if (dyn.d_tag == DT_PLTGOT) {
|
1115
|
+
info->got_addr = dyn.d_un.d_ptr;
|
1116
|
+
}
|
1082
1117
|
else if (dyn.d_tag == DT_PLTRELSZ) {
|
1083
1118
|
info->plt_size = dyn.d_un.d_val;
|
1084
1119
|
}
|
@@ -1108,8 +1143,8 @@ dissect_elf(struct elf_info *info, int find_debug)
|
|
1108
1143
|
|
1109
1144
|
data = elf_getdata(scn, NULL);
|
1110
1145
|
if (data == NULL || elf_getdata(scn, data) != NULL
|
1111
|
-
|| shdr.sh_size != data->d_size || data->d_off) {
|
1112
|
-
dbg_printf("Couldn't get .dynstr data");
|
1146
|
+
|| shdr.sh_size != data->d_size) {// condition true on 32bit: || data->d_off) {
|
1147
|
+
dbg_printf("Couldn't get .dynstr data\n");
|
1113
1148
|
ret = 1;
|
1114
1149
|
goto out;
|
1115
1150
|
}
|
@@ -1132,6 +1167,8 @@ dissect_elf(struct elf_info *info, int find_debug)
|
|
1132
1167
|
else if (shdr.sh_type == SHT_PROGBITS) {
|
1133
1168
|
if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".plt") == 0) {
|
1134
1169
|
info->plt_addr = shdr.sh_addr;
|
1170
|
+
} else if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".got.plt") == 0) {
|
1171
|
+
info->got_addr = shdr.sh_addr;
|
1135
1172
|
} else if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".gnu_debuglink") == 0) {
|
1136
1173
|
dbg_printf("gnu_debuglink section found\n", shdr.sh_size);
|
1137
1174
|
if ((info->debuglink_data = elf_getdata(scn, NULL)) == NULL ||
|
@@ -1214,9 +1251,6 @@ bin_init()
|
|
1214
1251
|
{
|
1215
1252
|
Dwarf_Error dwrf_err;
|
1216
1253
|
|
1217
|
-
ASSERT_ON_COMPILE(sizeof(unsigned long) == sizeof(GElf_Addr));
|
1218
|
-
ASSERT_ON_COMPILE(sizeof(unsigned long) == sizeof(Elf64_Addr));
|
1219
|
-
|
1220
1254
|
ruby_info = calloc(1, sizeof(*ruby_info));
|
1221
1255
|
|
1222
1256
|
if (!ruby_info) {
|
data/ext/extconf.rb
CHANGED
@@ -186,7 +186,7 @@ if have_header('mach-o/dyld.h')
|
|
186
186
|
expressions << ["address__#{name}", "&#{name}"]
|
187
187
|
end
|
188
188
|
|
189
|
-
pid = fork{sleep}
|
189
|
+
pid = fork{sleep while true}
|
190
190
|
output = IO.popen('gdb --interpreter=mi --quiet', 'w+') do |io|
|
191
191
|
io.puts "attach #{pid}"
|
192
192
|
expressions.each do |name, expr|
|
data/ext/memprof.c
CHANGED
@@ -346,6 +346,35 @@ memprof_track(int argc, VALUE *argv, VALUE self)
|
|
346
346
|
return Qnil;
|
347
347
|
}
|
348
348
|
|
349
|
+
static json_gen_status
|
350
|
+
json_gen_id(json_gen gen, ID id)
|
351
|
+
{
|
352
|
+
if (id) {
|
353
|
+
if (id < 100)
|
354
|
+
return json_gen_format(gen, ":%c", id);
|
355
|
+
else
|
356
|
+
return json_gen_format(gen, ":%s", rb_id2name(id));
|
357
|
+
} else
|
358
|
+
return json_gen_null(gen);
|
359
|
+
}
|
360
|
+
|
361
|
+
static json_gen_status
|
362
|
+
json_gen_value(json_gen gen, VALUE obj)
|
363
|
+
{
|
364
|
+
if (FIXNUM_P(obj))
|
365
|
+
return json_gen_integer(gen, NUM2LONG(obj));
|
366
|
+
else if (NIL_P(obj) || obj == Qundef)
|
367
|
+
return json_gen_null(gen);
|
368
|
+
else if (obj == Qtrue)
|
369
|
+
return json_gen_bool(gen, 1);
|
370
|
+
else if (obj == Qfalse)
|
371
|
+
return json_gen_bool(gen, 0);
|
372
|
+
else if (SYMBOL_P(obj))
|
373
|
+
return json_gen_id(gen, SYM2ID(obj));
|
374
|
+
else
|
375
|
+
return json_gen_pointer(gen, (void*)obj);
|
376
|
+
}
|
377
|
+
|
349
378
|
static json_gen_config fancy_conf = { .beautify = 1, .indentString = " " };
|
350
379
|
static json_gen_config basic_conf = { .beautify = 0, .indentString = " " };
|
351
380
|
|
@@ -469,7 +498,7 @@ memprof_trace_request(VALUE self, VALUE env)
|
|
469
498
|
|
470
499
|
json_gen_cstr(gen, "start");
|
471
500
|
gettimeofday(&now, NULL);
|
472
|
-
json_gen_integer(gen, (now.tv_sec *
|
501
|
+
json_gen_integer(gen, (now.tv_sec * 1000) + (now.tv_usec / 1000));
|
473
502
|
|
474
503
|
json_gen_cstr(gen, "tracers");
|
475
504
|
json_gen_map_open(gen);
|
@@ -533,6 +562,14 @@ memprof_trace_request(VALUE self, VALUE env)
|
|
533
562
|
DUMP_HASH_ENTRY("QUERY_STRING");
|
534
563
|
|
535
564
|
json_gen_map_close(gen);
|
565
|
+
|
566
|
+
if (RTEST(ret) && BUILTIN_TYPE(ret) == T_ARRAY) {
|
567
|
+
json_gen_cstr(gen, "response");
|
568
|
+
json_gen_map_open(gen);
|
569
|
+
json_gen_cstr(gen, "code");
|
570
|
+
json_gen_value(gen, RARRAY_PTR(ret)[0]);
|
571
|
+
json_gen_map_close(gen);
|
572
|
+
}
|
536
573
|
}
|
537
574
|
|
538
575
|
json_gen_cstr(gen, "time");
|
@@ -568,35 +605,6 @@ memprof_trace_request(VALUE self, VALUE env)
|
|
568
605
|
#define RSTRING_LEN(str) RSTRING(str)->len
|
569
606
|
#endif
|
570
607
|
|
571
|
-
static json_gen_status
|
572
|
-
json_gen_id(json_gen gen, ID id)
|
573
|
-
{
|
574
|
-
if (id) {
|
575
|
-
if (id < 100)
|
576
|
-
return json_gen_format(gen, ":%c", id);
|
577
|
-
else
|
578
|
-
return json_gen_format(gen, ":%s", rb_id2name(id));
|
579
|
-
} else
|
580
|
-
return json_gen_null(gen);
|
581
|
-
}
|
582
|
-
|
583
|
-
static json_gen_status
|
584
|
-
json_gen_value(json_gen gen, VALUE obj)
|
585
|
-
{
|
586
|
-
if (FIXNUM_P(obj))
|
587
|
-
return json_gen_integer(gen, NUM2LONG(obj));
|
588
|
-
else if (NIL_P(obj) || obj == Qundef)
|
589
|
-
return json_gen_null(gen);
|
590
|
-
else if (obj == Qtrue)
|
591
|
-
return json_gen_bool(gen, 1);
|
592
|
-
else if (obj == Qfalse)
|
593
|
-
return json_gen_bool(gen, 0);
|
594
|
-
else if (SYMBOL_P(obj))
|
595
|
-
return json_gen_id(gen, SYM2ID(obj));
|
596
|
-
else
|
597
|
-
return json_gen_pointer(gen, (void*)obj);
|
598
|
-
}
|
599
|
-
|
600
608
|
static int
|
601
609
|
each_hash_entry(st_data_t key, st_data_t record, st_data_t arg)
|
602
610
|
{
|
@@ -1942,7 +1950,9 @@ Init_memprof()
|
|
1942
1950
|
install_objects_tracer();
|
1943
1951
|
install_fd_tracer();
|
1944
1952
|
install_mysql_tracer();
|
1953
|
+
install_postgres_tracer();
|
1945
1954
|
install_memcache_tracer();
|
1955
|
+
install_resources_tracer();
|
1946
1956
|
|
1947
1957
|
gc_hook = Data_Wrap_Struct(rb_cObject, sourcefile_marker, NULL, NULL);
|
1948
1958
|
rb_global_variable(&gc_hook);
|
data/ext/tracer.h
CHANGED
@@ -41,6 +41,8 @@ extern void install_malloc_tracer();
|
|
41
41
|
extern void install_gc_tracer();
|
42
42
|
extern void install_fd_tracer();
|
43
43
|
extern void install_mysql_tracer();
|
44
|
+
extern void install_postgres_tracer();
|
44
45
|
extern void install_objects_tracer();
|
45
46
|
extern void install_memcache_tracer();
|
47
|
+
extern void install_resources_tracer();
|
46
48
|
#endif
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include <errno.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <string.h>
|
6
|
+
#include <sys/time.h>
|
7
|
+
|
8
|
+
#include "arch.h"
|
9
|
+
#include "bin_api.h"
|
10
|
+
#include "json.h"
|
11
|
+
#include "tracer.h"
|
12
|
+
#include "tramp.h"
|
13
|
+
#include "util.h"
|
14
|
+
|
15
|
+
struct memprof_postgres_stats {
|
16
|
+
size_t query_calls;
|
17
|
+
};
|
18
|
+
|
19
|
+
static struct tracer tracer;
|
20
|
+
static struct memprof_postgres_stats stats;
|
21
|
+
static void * (*orig_PQexec)(void *postgres, const char *stmt);
|
22
|
+
|
23
|
+
static void *
|
24
|
+
PQexec_tramp(void *postgres, const char *stmt) {
|
25
|
+
void *ret;
|
26
|
+
|
27
|
+
ret = orig_PQexec(postgres, stmt);
|
28
|
+
stats.query_calls++;
|
29
|
+
|
30
|
+
return ret;
|
31
|
+
}
|
32
|
+
|
33
|
+
static void
|
34
|
+
postgres_trace_start() {
|
35
|
+
static int inserted = 0;
|
36
|
+
|
37
|
+
if (!inserted)
|
38
|
+
inserted = 1;
|
39
|
+
else
|
40
|
+
return;
|
41
|
+
|
42
|
+
orig_PQexec = bin_find_symbol("PQexec", NULL, 1);
|
43
|
+
if (orig_PQexec)
|
44
|
+
insert_tramp("PQexec", PQexec_tramp);
|
45
|
+
}
|
46
|
+
|
47
|
+
static void
|
48
|
+
postgres_trace_stop() {
|
49
|
+
}
|
50
|
+
|
51
|
+
static void
|
52
|
+
postgres_trace_reset() {
|
53
|
+
memset(&stats, 0, sizeof(stats));
|
54
|
+
}
|
55
|
+
|
56
|
+
static void
|
57
|
+
postgres_trace_dump(json_gen gen) {
|
58
|
+
if (stats.query_calls > 0) {
|
59
|
+
json_gen_cstr(gen, "queries");
|
60
|
+
json_gen_integer(gen, stats.query_calls);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
void install_postgres_tracer()
|
65
|
+
{
|
66
|
+
tracer.start = postgres_trace_start;
|
67
|
+
tracer.stop = postgres_trace_stop;
|
68
|
+
tracer.reset = postgres_trace_reset;
|
69
|
+
tracer.dump = postgres_trace_dump;
|
70
|
+
tracer.id = "postgres";
|
71
|
+
|
72
|
+
trace_insert(&tracer);
|
73
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
#include <sys/time.h>
|
5
|
+
#include <sys/resource.h>
|
6
|
+
|
7
|
+
#include "json.h"
|
8
|
+
#include "tracer.h"
|
9
|
+
#include "tramp.h"
|
10
|
+
#include "util.h"
|
11
|
+
|
12
|
+
struct memprof_resources_stats {
|
13
|
+
long nsignals;
|
14
|
+
|
15
|
+
long inblock;
|
16
|
+
long oublock;
|
17
|
+
|
18
|
+
double utime;
|
19
|
+
double stime;
|
20
|
+
};
|
21
|
+
|
22
|
+
static struct tracer tracer;
|
23
|
+
static struct memprof_resources_stats stats;
|
24
|
+
|
25
|
+
#define TVAL_TO_DBL(tv) ((double)tv.tv_sec + (double)tv.tv_usec * 1e-6)
|
26
|
+
|
27
|
+
static void
|
28
|
+
resources_trace_start() {
|
29
|
+
struct rusage usage;
|
30
|
+
getrusage(RUSAGE_SELF, &usage);
|
31
|
+
|
32
|
+
stats.nsignals = -usage.ru_nsignals;
|
33
|
+
|
34
|
+
stats.inblock = -usage.ru_inblock;
|
35
|
+
stats.oublock = -usage.ru_oublock;
|
36
|
+
|
37
|
+
stats.stime = -TVAL_TO_DBL(usage.ru_stime);
|
38
|
+
stats.utime = -TVAL_TO_DBL(usage.ru_utime);
|
39
|
+
}
|
40
|
+
|
41
|
+
static void
|
42
|
+
resources_trace_dump(json_gen gen) {
|
43
|
+
{ // calculate diff before dump, since stop is called after dump
|
44
|
+
struct rusage usage;
|
45
|
+
getrusage(RUSAGE_SELF, &usage);
|
46
|
+
|
47
|
+
stats.nsignals += usage.ru_nsignals;
|
48
|
+
|
49
|
+
stats.inblock += usage.ru_inblock;
|
50
|
+
stats.oublock += usage.ru_oublock;
|
51
|
+
|
52
|
+
stats.stime += TVAL_TO_DBL(usage.ru_stime);
|
53
|
+
stats.utime += TVAL_TO_DBL(usage.ru_utime);
|
54
|
+
}
|
55
|
+
|
56
|
+
json_gen_cstr(gen, "signals");
|
57
|
+
json_gen_integer(gen, stats.nsignals);
|
58
|
+
|
59
|
+
json_gen_cstr(gen, "inputs");
|
60
|
+
json_gen_integer(gen, stats.inblock);
|
61
|
+
|
62
|
+
json_gen_cstr(gen, "outputs");
|
63
|
+
json_gen_integer(gen, stats.oublock);
|
64
|
+
|
65
|
+
json_gen_cstr(gen, "stime");
|
66
|
+
json_gen_double(gen, stats.stime);
|
67
|
+
|
68
|
+
json_gen_cstr(gen, "utime");
|
69
|
+
json_gen_double(gen, stats.utime);
|
70
|
+
}
|
71
|
+
|
72
|
+
static void
|
73
|
+
resources_trace_stop() {
|
74
|
+
}
|
75
|
+
|
76
|
+
static void
|
77
|
+
resources_trace_reset() {
|
78
|
+
}
|
79
|
+
|
80
|
+
void install_resources_tracer()
|
81
|
+
{
|
82
|
+
tracer.start = resources_trace_start;
|
83
|
+
tracer.stop = resources_trace_stop;
|
84
|
+
tracer.reset = resources_trace_reset;
|
85
|
+
tracer.dump = resources_trace_dump;
|
86
|
+
tracer.id = "resource";
|
87
|
+
|
88
|
+
trace_insert(&tracer);
|
89
|
+
}
|
data/memprof.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'memprof'
|
3
|
-
s.version = '0.3.
|
4
|
-
s.date = '2010-04-13'
|
3
|
+
s.version = '0.3.7'
|
5
4
|
s.summary = 'Ruby Memory Profiler'
|
6
5
|
s.description = "Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM"
|
7
6
|
s.homepage = "http://github.com/ice799/memprof"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memprof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
9
|
+
- 7
|
10
|
+
version: 0.3.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Joe Damato
|
@@ -18,7 +18,7 @@ autorequire:
|
|
18
18
|
bindir: bin
|
19
19
|
cert_chain: []
|
20
20
|
|
21
|
-
date:
|
21
|
+
date: 2011-01-28 00:00:00 -08:00
|
22
22
|
default_executable:
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
@@ -64,7 +64,7 @@ extra_rdoc_files: []
|
|
64
64
|
|
65
65
|
files:
|
66
66
|
- .gitignore
|
67
|
-
- README.
|
67
|
+
- README.md
|
68
68
|
- Rakefile
|
69
69
|
- bin/memprof
|
70
70
|
- ext/arch.h
|
@@ -89,6 +89,8 @@ files:
|
|
89
89
|
- ext/tracers/memory.c
|
90
90
|
- ext/tracers/mysql.c
|
91
91
|
- ext/tracers/objects.c
|
92
|
+
- ext/tracers/postgres.c
|
93
|
+
- ext/tracers/resources.c
|
92
94
|
- ext/tramp.c
|
93
95
|
- ext/tramp.h
|
94
96
|
- ext/util.c
|
@@ -134,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
136
|
requirements: []
|
135
137
|
|
136
138
|
rubyforge_project:
|
137
|
-
rubygems_version: 1.
|
139
|
+
rubygems_version: 1.4.2
|
138
140
|
signing_key:
|
139
141
|
specification_version: 3
|
140
142
|
summary: Ruby Memory Profiler
|
data/README.rdoc
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
= memprof (c) Joe Damato @joedamato http://timetobleed.com
|
2
|
-
|
3
|
-
Memprof is a Ruby level memory profiler that can help you find reference leaks in your application.
|
4
|
-
Memprof can also do very lightweight function call tracing to help you figure out which system calls, and library calls your code causes.
|
5
|
-
|
6
|
-
== Installing
|
7
|
-
|
8
|
-
gem install memprof
|
9
|
-
|
10
|
-
== Usage
|
11
|
-
|
12
|
-
== Memory Tracking
|
13
|
-
|
14
|
-
== Blocks (memory tracking)
|
15
|
-
|
16
|
-
Memprof.track {
|
17
|
-
100.times{ "abc" }
|
18
|
-
100.times{ 1.23 + 1 }
|
19
|
-
100.times{ Module.new }
|
20
|
-
}
|
21
|
-
|
22
|
-
Outputs:
|
23
|
-
|
24
|
-
100 file.rb:2:String
|
25
|
-
100 file.rb:3:Float
|
26
|
-
100 file.rb:4:Module
|
27
|
-
|
28
|
-
== Rails requests (memory tracking)
|
29
|
-
|
30
|
-
Use the Memprof::Middleware
|
31
|
-
|
32
|
-
== Dump objects
|
33
|
-
|
34
|
-
Memprof.dump {
|
35
|
-
"hello" + "world"
|
36
|
-
}
|
37
|
-
|
38
|
-
Outputs:
|
39
|
-
|
40
|
-
{
|
41
|
-
"_id": "0x19c610",
|
42
|
-
"file": "file.rb",
|
43
|
-
"line": 2,
|
44
|
-
"type": "string",
|
45
|
-
"class": "0x1ba7f0",
|
46
|
-
"class_name": "String",
|
47
|
-
"length": 10,
|
48
|
-
"data": "helloworld"
|
49
|
-
}
|
50
|
-
|
51
|
-
You can dump any Ruby object you want.
|
52
|
-
|
53
|
-
== Dumping the entire heap
|
54
|
-
|
55
|
-
Memprof.dump_all("file.json")
|
56
|
-
|
57
|
-
This will dump out every single live object as json to /tmp/file.json
|
58
|
-
|
59
|
-
== Less elegant tracking usage:
|
60
|
-
|
61
|
-
require 'memprof'
|
62
|
-
Memprof.start
|
63
|
-
|
64
|
-
# ruby code
|
65
|
-
|
66
|
-
Memprof.stats
|
67
|
-
|
68
|
-
# more ruby code
|
69
|
-
|
70
|
-
Memprof.stats
|
71
|
-
Memprof.stop
|
72
|
-
|
73
|
-
The above code will output 2 summaries, allowing you to compare which objects were
|
74
|
-
destroyed and which are still around.
|
75
|
-
|
76
|
-
Memprof.stats also takes an (optional) file name to write the output to a file.
|
77
|
-
|
78
|
-
== Function call tracing
|
79
|
-
|
80
|
-
This system is under development and the API may change without warning.
|
81
|
-
|
82
|
-
You can use the middleware Memprof::Tracer to output function tracing and request information for
|
83
|
-
each request that comes in to your app.
|
84
|
-
|
85
|
-
== Compatibility
|
86
|
-
|
87
|
-
You must have debug symbols installed or a an unstripped version of Ruby.
|
88
|
-
|
89
|
-
To install debug symbols on Debian-like systems:
|
90
|
-
|
91
|
-
apt-get install libruby1.8-dbg
|
92
|
-
|
93
|
-
Not supporting:
|
94
|
-
* OSX default Ruby
|
95
|
-
* Stripped Ruby binaries without debug symbols
|
96
|
-
* Any and all Windows Ruby builds
|
97
|
-
* Ruby 1.9+ on all systems
|
98
|
-
* 32bit systems
|
99
|
-
|
100
|
-
Supporting:
|
101
|
-
* Linux (enable-shared AND disable-shared):
|
102
|
-
* x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
103
|
-
* x86_64 builds of MRI Ruby
|
104
|
-
|
105
|
-
* Snow Leopard (enable-shared AND disable-shared):
|
106
|
-
* x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
107
|
-
* x86_64 builds of MRI Ruby
|
108
|
-
|
109
|
-
Coming soon:
|
110
|
-
|
111
|
-
Official support for Ruby 1.9
|
112
|
-
Official support for i386/i686
|
113
|
-
|
114
|
-
== Special Thanks
|
115
|
-
* Jake Douglas for the Mach O/snow leopard support.
|
116
|
-
* Aman Gupta for various bug fixes and other cleanup.
|
117
|
-
* Rob Benson for 1.9 support and cleanup.
|
118
|
-
* Paul Barry for force_gc support in Memprof::Middleware
|