rbtrace 0.1.0

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/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
+