rbtrace 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
@@ -0,0 +1,115 @@
1
+ # rbtrace
2
+
3
+ like strace, but for ruby code
4
+
5
+ ## usage
6
+
7
+ ### require rbtrace into a process
8
+
9
+ % cat server.rb
10
+ require 'ext/rbtrace'
11
+
12
+ class String
13
+ def multiply_vowels(num)
14
+ gsub(/[aeiou]/){ |m| m*num }
15
+ end
16
+ end
17
+
18
+ while true
19
+ proc {
20
+ Dir.chdir("/tmp") do
21
+ Dir.pwd
22
+ Process.pid
23
+ 'hello'.multiply_vowels(3)
24
+ sleep rand*0.5
25
+ end
26
+ }.call
27
+ end
28
+
29
+ ### run the process
30
+
31
+ % ruby server.rb &
32
+ [1] 95532
33
+
34
+ ### trace a function using the process's pid
35
+
36
+ % ./bin/rbtrace $! sleep
37
+
38
+ Kernel#sleep <0.329042>
39
+ Kernel#sleep <0.035732>
40
+ Kernel#sleep <0.291189>
41
+ Kernel#sleep <0.355800>
42
+ Kernel#sleep <0.238176>
43
+ Kernel#sleep <0.147345>
44
+ Kernel#sleep <0.238686>
45
+ Kernel#sleep <0.239668>
46
+ Kernel#sleep <0.035512>
47
+ ^C./bin/rbtrace:113: Interrupt
48
+
49
+ ### trace multiple functions
50
+
51
+ % ./bin/rbtrace $! sleep Dir.chdir Dir.pwd Process.pid "String#gsub" "String#*"
52
+
53
+ Dir.chdir
54
+ Dir.pwd <0.000094>
55
+ Process.pid <0.000016>
56
+ String#gsub
57
+ String#* <0.000020>
58
+ String#* <0.000020>
59
+ String#gsub <0.000072>
60
+ Kernel#sleep <0.369630>
61
+ Dir.chdir <0.370220>
62
+
63
+ Dir.chdir
64
+ Dir.pwd <0.000088>
65
+ Process.pid <0.000017>
66
+ String#gsub
67
+ String#* <0.000020>
68
+ String#* <0.000020>
69
+ String#gsub <0.000071>
70
+ ^C./bin/rbtrace:113: Interrupt
71
+
72
+ ### trace all functions in a class/module
73
+
74
+ % ./bin/rbtrace $! "Kernel#"
75
+
76
+ Kernel#proc <0.000071>
77
+ Kernel#rand <0.000029>
78
+ Kernel#sleep <0.331857>
79
+ Kernel#proc <0.000019>
80
+ Kernel#rand <0.000010>
81
+ Kernel#sleep <0.296361>
82
+ Kernel#proc <0.000021>
83
+ Kernel#rand <0.000012>
84
+ Kernel#sleep <0.281067>
85
+ ^C./bin/rbtrace:113: Interrupt
86
+
87
+ ### get values of variables and other expressions
88
+
89
+ % ./bin/rbtrace $! "String#gsub(self)" "String#*(self)" "String#multiply_vowels(self, self.length, num)"
90
+
91
+ String#multiply_vowels(self="hello", self.length=5, num=3)
92
+ String#gsub(self="hello")
93
+ String#*(self="e") <0.000021>
94
+ String#*(self="o") <0.000019>
95
+ String#gsub <0.000097>
96
+ String#multiply_vowels <0.000203>
97
+
98
+ ^C./bin/rbtrace:113: Interrupt
99
+
100
+ ### watch for method calls slower than 250ms
101
+
102
+ % ./bin/rbtrace $! watch 250
103
+ Kernel#sleep <0.402916>
104
+ Dir.chdir <0.403122>
105
+ Proc#call <0.403152>
106
+
107
+ Kernel#sleep <0.390635>
108
+ Dir.chdir <0.390937>
109
+ Proc#call <0.390983>
110
+
111
+ Kernel#sleep <0.399413>
112
+ Dir.chdir <0.399753>
113
+ Proc#call <0.399797>
114
+
115
+ ^C./bin/rbtrace:113: Interrupt
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'ffi'
4
+
5
+ module RBTracer
6
+ def self.process_line(line)
7
+ @tracers ||= {}
8
+ @nesting ||= 0
9
+ @last_tracer ||= nil
10
+
11
+ time, event, id, *args = line.strip.split(',')
12
+ time = time.to_i
13
+ id = id.to_i
14
+ tracer = @tracers[id] if id > -1
15
+
16
+ case event
17
+ when 'add'
18
+ if id == -1
19
+ puts line
20
+ else
21
+ name = args.first
22
+ @tracers[id] = {
23
+ :name => name,
24
+ :times => [],
25
+ :names => [],
26
+ :exprs => {},
27
+ :last => false,
28
+ :arglist => false
29
+ }
30
+ end
31
+
32
+ when 'remove'
33
+ if id == -1
34
+ puts line
35
+ else
36
+ @tracers.delete(id)
37
+ end
38
+
39
+ when 'newexpr'
40
+ expr_id, expr = *args
41
+ expr_id = expr_id.to_i
42
+
43
+ if expr_id > -1 and tracer
44
+ tracer[:exprs][expr_id] = expr
45
+ end
46
+
47
+ when 'exprval'
48
+ expr_id, val = *args
49
+ expr_id = expr_id.to_i
50
+ expr = tracer[:exprs][expr_id]
51
+
52
+ if tracer[:arglist]
53
+ print ', '
54
+ else
55
+ print '('
56
+ end
57
+
58
+ print "#{expr}="
59
+ print val
60
+ tracer[:arglist] = true
61
+
62
+ when 'call','ccall'
63
+ method, is_singleton, klass = *args
64
+ is_singleton = (is_singleton == '1')
65
+ name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
66
+ name += method
67
+
68
+ tracer[:times] << time
69
+ tracer[:names] << name
70
+
71
+ if @last_tracer and @last_tracer[:arglist]
72
+ print ')'
73
+ @last_tracer[:arglist] = false
74
+ end
75
+ puts
76
+ print ' '*@nesting if @nesting > 0
77
+ print name
78
+
79
+ @nesting += 1
80
+ @last_tracer = tracer
81
+ tracer[:last] = name
82
+
83
+ when 'return','creturn'
84
+ @nesting -= 1 if @nesting > 0
85
+
86
+ if start = tracer[:times].pop
87
+ name = tracer[:names].pop
88
+ diff = time - start
89
+ @last_tracer[:arglist] = false if @last_tracer and @last_tracer[:last] != name
90
+
91
+ print ')' if @last_tracer and @last_tracer[:arglist]
92
+
93
+ unless tracer == @last_tracer and @last_tracer[:last] == name
94
+ puts
95
+ print ' '*@nesting if @nesting > 0
96
+ print name
97
+ end
98
+ print ' <%f>' % (diff/1_000_000.0)
99
+ puts if @nesting == 0 and (tracer != @last_tracer || @last_tracer[:last] != name)
100
+ end
101
+
102
+ tracer[:arglist] = false
103
+
104
+ when 'slow', 'cslow'
105
+ diff, nesting, method, is_singleton, klass = *args
106
+ nesting = nesting.to_i
107
+ diff = diff.to_i
108
+
109
+ is_singleton = (is_singleton == '1')
110
+ name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
111
+ name += method
112
+
113
+ print ' '*nesting if nesting > 0
114
+ print name
115
+ print ' '
116
+ puts "<%f>" % (diff/1_000_000.0)
117
+ puts if nesting == 0
118
+
119
+ else
120
+ puts "unknown event: #{line}"
121
+
122
+ end
123
+ rescue => e
124
+ puts "error on: #{line}"
125
+ raise e
126
+ end
127
+ end
128
+
129
+ class EventMsg < FFI::Struct
130
+ BUF_SIZE = RUBY_PLATFORM =~ /linux/ ? 256 : 120
131
+ IPC_NOWAIT = 004000
132
+
133
+ layout :mtype, :long,
134
+ :buf, [:char, BUF_SIZE]
135
+
136
+ def self.send_cmd(q, str)
137
+ msg = EventMsg.new
138
+ msg[:mtype] = 1
139
+ msg[:buf].to_ptr.put_string(0, str)
140
+
141
+ ret = MsgQ.msgsnd(q, msg, BUF_SIZE, 0)
142
+ FFI::LastError.raise if ret == -1
143
+ end
144
+
145
+ def self.recv_cmd(q, block=true)
146
+ msg = EventMsg.new
147
+ ret = MsgQ.msgrcv(q, msg, BUF_SIZE, 0, block ? 0 : IPC_NOWAIT)
148
+ if ret == -1
149
+ if !block and [Errno::EAGAIN, Errno::ENOMSG].include?(FFI::LastError.exception)
150
+ return nil
151
+ end
152
+
153
+ FFI::LastError.raise
154
+ end
155
+
156
+ msg[:buf].to_ptr.read_string
157
+ end
158
+ end
159
+
160
+ module FFI::LastError
161
+ def self.exception
162
+ Errno::constants.map(&Errno.method(:const_get)).find{ |c| c.const_get(:Errno) == error }
163
+ end
164
+ def self.raise(msg=nil)
165
+ Kernel.raise exception, msg
166
+ end
167
+ end
168
+
169
+ module MsgQ
170
+ extend FFI::Library
171
+
172
+ ffi_lib 'c'
173
+ attach_function :msgget, [:int, :int], :int
174
+ attach_function :msgrcv, [:int, EventMsg.ptr, :size_t, :long, :int], :int
175
+ attach_function :msgsnd, [:int, EventMsg.ptr, :size_t, :int], :int
176
+ end
177
+
178
+ if (pids = `ps ax -o pid`.split("\n").map{ |p| p.strip.to_i }).any?
179
+ ipcs = `ipcs -q`.split("\n").grep(/^q/).map{ |line| line.strip.split[2] }
180
+ ipcs.each do |ipci|
181
+ next if ipci.match(/^0xf/)
182
+
183
+ qi = ipci.to_i(16)
184
+ qo = 0xffffffff - qi + 1
185
+ ipco = "0x#{qo.to_s(16)}"
186
+
187
+ if ipcs.include?(ipco) and !pids.include?(qi)
188
+ STDERR.puts "** removing stale message queue pair: #{ipci}/#{ipco}"
189
+ system("ipcrm -Q #{ipci} -Q #{ipco}")
190
+ end
191
+ end
192
+ end
193
+
194
+ if File.exists?(msgmnb = "/proc/sys/kernel/msgmnb")
195
+ max = File.read(msgmnb).to_i
196
+ if max < 512*1024
197
+ STDERR.puts "** run `sudo sysctl kernel.msgmnb=#{512*1024}` to prevent losing events"
198
+ end
199
+ end
200
+
201
+ raise 'invalid pid' unless ARGV[0]
202
+
203
+ begin
204
+ raise ArgumentError unless pid = ARGV[0]
205
+ pid = pid.to_i
206
+ raise ArgumentError unless pid > 0
207
+ Process.kill(0,pid)
208
+ rescue TypeError, ArgumentError
209
+ raise 'pid required'
210
+ rescue Errno::ESRCH
211
+ raise 'invalid pid'
212
+ rescue Errno::EPERM
213
+ raise 'could not signal process (run as root)'
214
+ end
215
+
216
+ funcs = [ARGV[1] || 'sleep']
217
+ if ARGV.size > 2
218
+ funcs += ARGV[2..-1]
219
+ end
220
+
221
+ qi = MsgQ.msgget(pid, 0666)
222
+ qo = MsgQ.msgget(-pid, 0666)
223
+
224
+ if qi == -1 || qo == -1
225
+ raise 'invalid pid'
226
+ else
227
+ begin
228
+ if funcs.first == 'watch'
229
+ EventMsg.send_cmd(qo, funcs.join(','))
230
+ Process.kill 'URG', pid
231
+ else
232
+ funcs.each do |func|
233
+ func.strip!
234
+ if func =~ /^(.+)\((.+?)\)$/
235
+ func, args = $1, $2
236
+ args = args.split(',').map{ |a| a.strip }
237
+ end
238
+
239
+ EventMsg.send_cmd(qo, "add,#{func}")
240
+ Process.kill 'URG', pid
241
+
242
+ if args and args.any?
243
+ args.each do |arg|
244
+ EventMsg.send_cmd(qo, "addexpr,#{arg}")
245
+ Process.kill 'URG', pid
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ while true
252
+ # block until a message arrives
253
+ lines = [EventMsg.recv_cmd(qi)]
254
+
255
+ # check to see if there are more messages and pull them off
256
+ # so the queue doesn't fill up in kernel land
257
+ 25.times do
258
+ break unless line = EventMsg.recv_cmd(qi, false)
259
+ lines << line
260
+ end
261
+
262
+ lines.each do |line|
263
+ RBTracer.process_line(line)
264
+ end
265
+ end
266
+ ensure
267
+ EventMsg.send_cmd(qo, funcs.first == 'watch' ? 'unwatch' : 'delall')
268
+ Process.kill 'URG', pid
269
+ end
270
+ end
@@ -0,0 +1,8 @@
1
+ require 'mkmf'
2
+
3
+ # increase message size on linux
4
+ if RUBY_PLATFORM =~ /linux/
5
+ $defs.push("-DBUF_SIZE=256")
6
+ end
7
+
8
+ create_makefile('rbtrace')
@@ -0,0 +1,593 @@
1
+ #include <inttypes.h>
2
+ #include <errno.h>
3
+ #include <signal.h>
4
+ #include <stdbool.h>
5
+ #include <stdio.h>
6
+ #include <stdint.h>
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+ #include <strings.h>
10
+ #include <sys/ipc.h>
11
+ #include <sys/msg.h>
12
+ #include <sys/time.h>
13
+ #include <sys/types.h>
14
+ #include <time.h>
15
+ #include <unistd.h>
16
+
17
+ #include <ruby.h>
18
+
19
+ #ifndef RUBY_VM
20
+ #include <node.h>
21
+ #include <intern.h>
22
+ #endif
23
+
24
+ #ifndef RSTRING_PTR
25
+ #define RSTRING_PTR(str) RSTRING(str)->ptr
26
+ #endif
27
+ #ifndef RSTRING_LEN
28
+ #define RSTRING_LEN(str) RSTRING(str)->len
29
+ #endif
30
+
31
+ static uint64_t
32
+ timeofday_usec()
33
+ {
34
+ struct timeval tv;
35
+ gettimeofday(&tv, NULL);
36
+ return (uint64_t)tv.tv_sec*1e6 + (uint64_t)tv.tv_usec;
37
+ }
38
+
39
+ #define MAX_EXPRS 10
40
+ struct rbtracer_t {
41
+ int id;
42
+
43
+ char *query;
44
+ VALUE self;
45
+ VALUE klass;
46
+ ID mid;
47
+
48
+ int num_exprs;
49
+ char *exprs[MAX_EXPRS];
50
+ };
51
+ typedef struct rbtracer_t rbtracer_t;
52
+
53
+ #define MAX_TRACERS 100
54
+ static rbtracer_t tracers[MAX_TRACERS];
55
+ static unsigned int num_tracers = 0;
56
+
57
+ key_t mqi_key, mqo_key;
58
+ int mqi_id = -1, mqo_id = -1;
59
+
60
+ #ifndef BUF_SIZE
61
+ #define BUF_SIZE 120
62
+ #endif
63
+
64
+ struct event_msg {
65
+ long mtype;
66
+ char buf[BUF_SIZE];
67
+ };
68
+
69
+ #define SEND_EVENT(format, ...) do {\
70
+ uint64_t usec = timeofday_usec();\
71
+ if (false) {\
72
+ fprintf(stderr, "%" PRIu64 "," format, usec, __VA_ARGS__);\
73
+ fprintf(stderr, "\n");\
74
+ } else {\
75
+ struct event_msg msg;\
76
+ int ret = -1, n = 0;\
77
+ \
78
+ msg.mtype = 1;\
79
+ snprintf(msg.buf, sizeof(msg.buf), "%" PRIu64 "," format, usec, __VA_ARGS__);\
80
+ \
81
+ for (n=0; n<10 && ret==-1; n++)\
82
+ ret = msgsnd(mqo_id, &msg, sizeof(msg)-sizeof(long), IPC_NOWAIT);\
83
+ if (ret == -1) {\
84
+ fprintf(stderr, "msgsnd(): %s\n", strerror(errno));\
85
+ struct msqid_ds stat;\
86
+ msgctl(mqo_id, IPC_STAT, &stat);\
87
+ fprintf(stderr, "cbytes: %lu, qbytes: %lu, qnum: %lu\n", stat.msg_cbytes, stat.msg_qbytes, stat.msg_qnum);\
88
+ }\
89
+ }\
90
+ } while (0)
91
+
92
+ static int in_event_hook = 0;
93
+ static bool event_hook_installed = false;
94
+
95
+ #define MAX_CALLS 32768
96
+ static struct {
97
+ bool enabled;
98
+ uint64_t call_times[ MAX_CALLS ];
99
+ int num_calls;
100
+ uint32_t threshold;
101
+ } slow_tracer = {
102
+ .enabled = false,
103
+ .num_calls = 0,
104
+ .threshold = 250
105
+ };
106
+
107
+ static void
108
+ #ifdef RUBY_VM
109
+ event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
110
+ #else
111
+ event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
112
+ #endif
113
+ {
114
+ // do not re-enter this function
115
+ // after this, must `goto out` instead of `return`
116
+ if (in_event_hook) return;
117
+ in_event_hook++;
118
+
119
+ // skip allocators
120
+ if (mid == ID_ALLOCATOR) goto out;
121
+
122
+ // normalize klass and check for class-level methods
123
+ bool singleton = 0;
124
+ if (klass) {
125
+ if (TYPE(klass) == T_ICLASS) {
126
+ klass = RBASIC(klass)->klass;
127
+ }
128
+ singleton = FL_TEST(klass, FL_SINGLETON);
129
+ }
130
+
131
+ // are we watching for any slow methods?
132
+ if (slow_tracer.enabled) {
133
+ uint64_t usec = timeofday_usec(), diff = 0;
134
+
135
+ switch (event) {
136
+ case RUBY_EVENT_C_CALL:
137
+ case RUBY_EVENT_CALL:
138
+ if (slow_tracer.num_calls < MAX_CALLS)
139
+ slow_tracer.call_times[ slow_tracer.num_calls ] = usec;
140
+
141
+ slow_tracer.num_calls++;
142
+ break;
143
+
144
+ case RUBY_EVENT_C_RETURN:
145
+ case RUBY_EVENT_RETURN:
146
+ if (slow_tracer.num_calls > 0) {
147
+ slow_tracer.num_calls--;
148
+
149
+ if (slow_tracer.num_calls < MAX_CALLS)
150
+ diff = usec - slow_tracer.call_times[ slow_tracer.num_calls ];
151
+ }
152
+ break;
153
+ }
154
+
155
+ if (diff > slow_tracer.threshold * 1e3) {
156
+ SEND_EVENT(
157
+ "%s,-1,%" PRIu64 ",%d,%s,%d,%s",
158
+ event == RUBY_EVENT_RETURN ? "slow" : "cslow",
159
+ diff,
160
+ slow_tracer.num_calls,
161
+ rb_id2name(mid),
162
+ singleton,
163
+ klass ? rb_class2name(singleton ? self : klass) : ""
164
+ );
165
+ }
166
+
167
+ goto out;
168
+ }
169
+
170
+ // are there specific methods we're waiting for?
171
+ if (num_tracers == 0) goto out;
172
+
173
+ int i, n;
174
+ rbtracer_t *tracer = NULL;
175
+
176
+ for (i=0, n=0; i<MAX_TRACERS && n<num_tracers; i++) {
177
+ if (tracers[i].query) {
178
+ n++;
179
+
180
+ if (!tracers[i].mid || tracers[i].mid == mid) {
181
+ if (!tracers[i].klass || tracers[i].klass == klass) {
182
+ if (!tracers[i].self || tracers[i].self == self) {
183
+ tracer = &tracers[i];
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ // no matching method tracer found, so bail!
191
+ if (!tracer) goto out;
192
+
193
+ switch (event) {
194
+ case RUBY_EVENT_C_CALL:
195
+ case RUBY_EVENT_CALL:
196
+ SEND_EVENT(
197
+ "%s,%d,%s,%d,%s",
198
+ event == RUBY_EVENT_CALL ? "call" : "ccall",
199
+ tracer->id,
200
+ rb_id2name(mid),
201
+ singleton,
202
+ klass ? rb_class2name(singleton ? self : klass) : ""
203
+ );
204
+
205
+ if (tracer->num_exprs) {
206
+ for (i=0; i<tracer->num_exprs; i++) {
207
+ char *expr = tracer->exprs[i];
208
+ size_t len = strlen(expr);
209
+ VALUE str = Qnil, val = Qnil;
210
+
211
+ if (len == 4 && strcmp("self", expr) == 0) {
212
+ val = rb_inspect(self);
213
+
214
+ } else if (event == RUBY_EVENT_CALL) {
215
+ char code[len+50];
216
+ snprintf(code, len+50, "(begin; %s; rescue Exception => e; e; end).inspect", expr);
217
+
218
+ str = rb_str_new2(code);
219
+ val = rb_obj_instance_eval(1, &str, self);
220
+ }
221
+
222
+ if (RTEST(val) && TYPE(val) == T_STRING) {
223
+ char *result = RSTRING_PTR(val);
224
+ SEND_EVENT(
225
+ "%s,%d,%d,%s",
226
+ "exprval",
227
+ tracer->id,
228
+ i,
229
+ result
230
+ );
231
+ }
232
+ }
233
+ }
234
+ break;
235
+
236
+ case RUBY_EVENT_C_RETURN:
237
+ case RUBY_EVENT_RETURN:
238
+ SEND_EVENT(
239
+ "%s,%d",
240
+ event == RUBY_EVENT_RETURN ? "return" : "creturn",
241
+ tracer->id
242
+ );
243
+ break;
244
+ }
245
+
246
+ out:
247
+ in_event_hook--;
248
+ }
249
+
250
+ static void
251
+ event_hook_install()
252
+ {
253
+ if (!event_hook_installed) {
254
+ rb_add_event_hook(
255
+ event_hook,
256
+ RUBY_EVENT_CALL | RUBY_EVENT_C_CALL |
257
+ RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN
258
+ #ifdef RB_EVENT_HOOKS_HAVE_CALLBACK_DATA
259
+ , 0
260
+ #endif
261
+ );
262
+ event_hook_installed = true;
263
+ }
264
+ }
265
+
266
+ static void
267
+ event_hook_remove()
268
+ {
269
+ if (event_hook_installed) {
270
+ rb_remove_event_hook(event_hook);
271
+ event_hook_installed = false;
272
+ }
273
+ }
274
+
275
+ static int
276
+ rbtracer_remove(char *query, int id)
277
+ {
278
+ int i;
279
+ int tracer_id = -1;
280
+ rbtracer_t *tracer = NULL;
281
+
282
+ if (query) {
283
+ for (i=0; i<MAX_TRACERS; i++) {
284
+ if (tracers[i].query) {
285
+ if (0 == strcmp(query, tracers[i].query)) {
286
+ tracer = &tracers[i];
287
+ break;
288
+ }
289
+ }
290
+ }
291
+ } else {
292
+ if (id >= MAX_TRACERS) goto out;
293
+ tracer = &tracers[id];
294
+ }
295
+
296
+ if (tracer->query) {
297
+ tracer_id = tracer->id;
298
+ tracer->mid = 0;
299
+
300
+ free(tracer->query);
301
+ tracer->query = NULL;
302
+
303
+ if (tracer->num_exprs) {
304
+ for(i=0; i<tracer->num_exprs; i++) {
305
+ free(tracer->exprs[i]);
306
+ tracer->exprs[i] = NULL;
307
+ }
308
+ tracer->num_exprs = 0;
309
+ }
310
+
311
+ num_tracers--;
312
+ if (num_tracers == 0)
313
+ event_hook_remove();
314
+ }
315
+
316
+ out:
317
+ SEND_EVENT(
318
+ "remove,%d,%s",
319
+ tracer_id,
320
+ query
321
+ );
322
+ return tracer_id;
323
+ }
324
+
325
+ static void
326
+ rbtracer_remove_all()
327
+ {
328
+ int i;
329
+ for (i=0; i<MAX_TRACERS; i++) {
330
+ if (tracers[i].query) {
331
+ rbtracer_remove(NULL, i);
332
+ }
333
+ }
334
+ }
335
+
336
+ static int
337
+ rbtracer_add(char *query)
338
+ {
339
+ int i;
340
+ int tracer_id = -1;
341
+ rbtracer_t *tracer = NULL;
342
+
343
+ if (num_tracers >= MAX_TRACERS) goto out;
344
+
345
+ for (i=0; i<MAX_TRACERS; i++) {
346
+ if (!tracers[i].query) {
347
+ tracer = &tracers[i];
348
+ tracer_id = i;
349
+ break;
350
+ }
351
+ }
352
+ if (!tracer) goto out;
353
+
354
+ char *idx, *method;
355
+ VALUE klass = 0, self = 0;
356
+ ID mid = 0;
357
+
358
+ if (NULL != (idx = rindex(query, '.'))) {
359
+ *idx = 0;
360
+ self = rb_eval_string_protect(query, 0);
361
+ *idx = '.';
362
+
363
+ method = idx+1;
364
+ } else if (NULL != (idx = rindex(query, '#'))) {
365
+ *idx = 0;
366
+ klass = rb_eval_string_protect(query, 0);
367
+ *idx = '#';
368
+
369
+ method = idx+1;
370
+ } else {
371
+ method = query;
372
+ }
373
+
374
+ if (method && *method) {
375
+ mid = rb_intern(method);
376
+ if (!mid) goto out;
377
+ } else if (klass || self) {
378
+ mid = 0;
379
+ } else {
380
+ goto out;
381
+ }
382
+
383
+ memset(tracer, 0, sizeof(*tracer));
384
+
385
+ tracer->id = tracer_id;
386
+ tracer->self = self;
387
+ tracer->klass = klass;
388
+ tracer->mid = mid;
389
+ tracer->query = strdup(query);
390
+ tracer->num_exprs = 0;
391
+
392
+ if (num_tracers == 0)
393
+ event_hook_install();
394
+
395
+ num_tracers++;
396
+
397
+ out:
398
+ SEND_EVENT(
399
+ "add,%d,%s",
400
+ tracer_id,
401
+ query
402
+ );
403
+ return tracer_id;
404
+ }
405
+
406
+ static void
407
+ rbtracer_add_expr(int id, char *expr)
408
+ {
409
+ int expr_id = -1;
410
+ int tracer_id = -1;
411
+ rbtracer_t *tracer = NULL;
412
+
413
+ if (id >= MAX_TRACERS) goto out;
414
+ tracer = &tracers[id];
415
+
416
+ if (tracer->query) {
417
+ tracer_id = tracer->id;
418
+
419
+ if (tracer->num_exprs < MAX_EXPRS) {
420
+ expr_id = tracer->num_exprs++;
421
+ tracer->exprs[expr_id] = strdup(expr);
422
+ }
423
+ }
424
+
425
+ out:
426
+ SEND_EVENT(
427
+ "newexpr,%d,%d,%s",
428
+ tracer_id,
429
+ expr_id,
430
+ expr
431
+ );
432
+ }
433
+
434
+ static void
435
+ rbtracer_watch(uint32_t threshold)
436
+ {
437
+ if (!slow_tracer.enabled) {
438
+ slow_tracer.num_calls = 0;
439
+ slow_tracer.threshold = threshold;
440
+ slow_tracer.enabled = true;
441
+
442
+ event_hook_install();
443
+ }
444
+ }
445
+
446
+ static void
447
+ rbtracer_unwatch()
448
+ {
449
+ if (slow_tracer.enabled) {
450
+ event_hook_remove();
451
+
452
+ slow_tracer.enabled = false;
453
+ }
454
+ }
455
+
456
+ static VALUE
457
+ rbtrace(VALUE self, VALUE query)
458
+ {
459
+ Check_Type(query, T_STRING);
460
+
461
+ char *str = RSTRING_PTR(query);
462
+ int tracer_id = -1;
463
+
464
+ tracer_id = rbtracer_add(str);
465
+ return tracer_id == -1 ? Qfalse : Qtrue;
466
+ }
467
+
468
+ static VALUE
469
+ untrace(VALUE self, VALUE query)
470
+ {
471
+ Check_Type(query, T_STRING);
472
+
473
+ char *str = RSTRING_PTR(query);
474
+ int tracer_id = -1;
475
+
476
+ tracer_id = rbtracer_remove(str, -1);
477
+ return tracer_id == -1 ? Qfalse : Qtrue;
478
+ }
479
+
480
+ static void
481
+ cleanup()
482
+ {
483
+ if (mqo_id != -1) {
484
+ msgctl(mqo_id, IPC_RMID, NULL);
485
+ mqo_id = -1;
486
+ }
487
+ if (mqi_id != -1) {
488
+ msgctl(mqi_id, IPC_RMID, NULL);
489
+ mqi_id = -1;
490
+ }
491
+ }
492
+
493
+ static void
494
+ cleanup_ruby(VALUE data)
495
+ {
496
+ cleanup();
497
+ }
498
+
499
+ static void
500
+ sigurg(int signal)
501
+ {
502
+ static int last_tracer_id = -1; // hax
503
+ if (mqi_id == -1) return;
504
+
505
+ struct event_msg msg;
506
+ char *query = NULL;
507
+ size_t len = 0;
508
+ int n = 0;
509
+
510
+ while (true) {
511
+ int ret = -1;
512
+
513
+ for (n=0; n<10 && ret==-1; n++)
514
+ ret = msgrcv(mqi_id, &msg, sizeof(msg)-sizeof(long), 0, IPC_NOWAIT);
515
+
516
+ if (ret == -1) {
517
+ break;
518
+ } else {
519
+ len = strlen(msg.buf);
520
+ if (msg.buf[len-1] == '\n')
521
+ msg.buf[len-1] = 0;
522
+
523
+ if (0 == strncmp("add,", msg.buf, 4)) {
524
+ query = msg.buf + 4;
525
+ last_tracer_id = rbtracer_add(query);
526
+
527
+ } else if (0 == strncmp("del,", msg.buf, 4)) {
528
+ query = msg.buf + 4;
529
+ rbtracer_remove(query, -1);
530
+
531
+ } else if (0 == strncmp("delall", msg.buf, 6)) {
532
+ rbtracer_remove_all();
533
+
534
+ } else if (0 == strncmp("addexpr,", msg.buf, 8)) {
535
+ query = msg.buf + 8;
536
+ rbtracer_add_expr(last_tracer_id, query);
537
+
538
+ } else if (0 == strncmp("watch,", msg.buf, 6)) {
539
+ int msec = 250;
540
+
541
+ query = msg.buf + 6;
542
+ if (query && *query)
543
+ msec = atoi(query);
544
+
545
+ rbtracer_watch(msec);
546
+
547
+ } else if (0 == strncmp("unwatch", msg.buf, 7)) {
548
+ rbtracer_unwatch();
549
+
550
+ }
551
+ }
552
+ }
553
+ }
554
+
555
+ void
556
+ Init_rbtrace()
557
+ {
558
+ atexit(cleanup);
559
+ rb_set_end_proc(cleanup_ruby, 0);
560
+
561
+ signal(SIGURG, sigurg);
562
+
563
+ memset(&tracers, 0, sizeof(tracers));
564
+
565
+ mqo_key = (key_t) getpid();
566
+ mqo_id = msgget(mqo_key, 0666 | IPC_CREAT);
567
+ if (mqo_id == -1)
568
+ rb_sys_fail("msgget");
569
+
570
+ mqi_key = (key_t) -getpid();
571
+ mqi_id = msgget(mqi_key, 0666 | IPC_CREAT);
572
+ if (mqi_id == -1)
573
+ rb_sys_fail("msgget");
574
+
575
+ /*
576
+ struct msqid_ds stat;
577
+ int ret;
578
+
579
+ msgctl(mqo_id, IPC_STAT, &stat);
580
+ printf("cbytes: %lu, qbytes: %lu, qnum: %lu\n", stat.msg_cbytes, stat.msg_qbytes, stat.msg_qnum);
581
+
582
+ stat.msg_qbytes += 10;
583
+ ret = msgctl(mqo_id, IPC_SET, &stat);
584
+ printf("cbytes: %lu, qbytes: %lu, qnum: %lu\n", stat.msg_cbytes, stat.msg_qbytes, stat.msg_qnum);
585
+ printf("ret: %d, errno: %d\n", ret, errno);
586
+
587
+ msgctl(mqo_id, IPC_STAT, &stat);
588
+ printf("cbytes: %lu, qbytes: %lu, qnum: %lu\n", stat.msg_cbytes, stat.msg_qbytes, stat.msg_qnum);
589
+ */
590
+
591
+ rb_define_method(rb_cObject, "rbtrace", rbtrace, 1);
592
+ rb_define_method(rb_cObject, "untrace", untrace, 1);
593
+ }
@@ -0,0 +1,57 @@
1
+ class Test
2
+ def call
3
+ self[:a] = :b
4
+ end
5
+ def []=(k,v)
6
+ Another.new.call
7
+ :ok
8
+ end
9
+ end
10
+
11
+ class Another
12
+ def call
13
+ self[:a] = :b
14
+ end
15
+ def []=(key, value)
16
+ (@hash ||= {})[key]=value
17
+ end
18
+ end
19
+
20
+ module Do
21
+ module It
22
+ def something
23
+ end
24
+ end
25
+ end
26
+
27
+ class Slow
28
+ include Do::It
29
+ def self.something
30
+ sleep 0.01
31
+ end
32
+ end
33
+
34
+ require 'rbtrace'
35
+ rbtrace 'Slow.something'
36
+ rbtrace 'Do::It#something'
37
+ rbtrace '[]='
38
+ rbtrace 'call'
39
+
40
+ 1.times do
41
+ Slow.something
42
+ Slow.new.something
43
+ Test.new.call
44
+ end
45
+
46
+ __END__
47
+
48
+ Slow.something <10047>
49
+ Do::It#something <4>
50
+ Test#call
51
+ Test#[]=
52
+ Another#call
53
+ Another#[]= <4>
54
+ <14>
55
+ <28>
56
+ <40>
57
+
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rbtrace'
3
+ s.version = '0.1.0'
4
+ s.homepage = 'http://github.com/tmm1/rbtrace'
5
+
6
+ s.authors = "Aman Gupta"
7
+ s.email = "aman@tmm1.net"
8
+
9
+ s.files = `git ls-files`.split("\n")
10
+ s.extensions = "ext/extconf.rb"
11
+
12
+ s.bindir = 'bin'
13
+ s.executables << 'rbtrace'
14
+
15
+ s.add_dependency 'ffi'
16
+
17
+ s.summary = 'rbtrace: like strace but for ruby code'
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'ext/rbtrace'
2
+
3
+ class String
4
+ def multiply_vowels(num)
5
+ gsub(/[aeiou]/){ |m| m*num }
6
+ end
7
+ end
8
+
9
+ while true
10
+ proc {
11
+ Dir.chdir("/tmp") do
12
+ Dir.pwd
13
+ Process.pid
14
+ 'hello'.multiply_vowels(3)
15
+ sleep rand*0.5
16
+ end
17
+ }.call
18
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbtrace
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Aman Gupta
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-14 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ffi
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description:
36
+ email: aman@tmm1.net
37
+ executables:
38
+ - rbtrace
39
+ extensions:
40
+ - ext/extconf.rb
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Gemfile
45
+ - README.md
46
+ - bin/rbtrace
47
+ - ext/extconf.rb
48
+ - ext/rbtrace.c
49
+ - ext/test.rb
50
+ - rbtrace.gemspec
51
+ - server.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/tmm1/rbtrace
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.4.2
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: "rbtrace: like strace but for ruby code"
86
+ test_files: []
87
+