memprof 0.3.6 → 0.3.7
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 +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
|