rbtrace 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -156,8 +156,12 @@ rbtrace works on ruby 1.8 and 1.9, running on linux or mac osx.
156
156
 
157
157
  * switch ipc to [msgpack](https://github.com/dhotson/msgpack/tree/master/c) instead of csv
158
158
  * add triggers to start tracing slow methods only inside another method
159
+ * syntax check expressions before adding them
160
+ * add special expressions for method args (_arg0_, _arguments_)
159
161
  * optimize local variable lookup to avoid instance_eval
160
- * use shared memory region for symbol table to avoid lookup on every event
161
- * use another shm for class name lookup, and wipe on every GC
162
+ * run process via bin/rbtrace (to trace rubygems and bootup time)
163
+ * let bin/rbtrace attach to multiple pids
164
+ * how to select on multiple msgrcv() targets?
165
+ * prefix pid to output in multiple pid mode
162
166
  * investigate mach_msg on osx since msgget(2) has hard kernel limits
163
167
 
data/bin/rbtrace CHANGED
@@ -142,9 +142,11 @@ class RBTracer
142
142
  raise ArgumentError, 'pid is not listening for messages, did you `require "rbtrace"`'
143
143
  end
144
144
 
145
+ @klasses = {}
146
+ @methods = {}
145
147
  @tracers = Hash.new{ |h,k|
146
148
  h[k] = {
147
- :name => nil,
149
+ :query => nil,
148
150
  :times => [],
149
151
  :names => [],
150
152
  :exprs => {},
@@ -152,12 +154,16 @@ class RBTracer
152
154
  :arglist => false
153
155
  }
154
156
  }
155
- @nesting = 0
157
+ @max_nesting = @nesting = 0
156
158
  @last_tracer = nil
159
+
157
160
  @out = STDOUT
158
161
  @prefix = ' '
162
+
159
163
  @show_time = false
160
164
  @show_duration = true
165
+
166
+ attach
161
167
  end
162
168
 
163
169
  # Watch for method calls slower than a threshold.
@@ -184,6 +190,7 @@ class RBTracer
184
190
  def add(methods)
185
191
  Array(methods).each do |func|
186
192
  func = func.strip
193
+ next if func.empty?
187
194
 
188
195
  if func =~ /^(.+)\((.+)\)$/
189
196
  func, args = $1, $2
@@ -198,6 +205,18 @@ class RBTracer
198
205
  end
199
206
  end
200
207
 
208
+ # Attach to the process.
209
+ #
210
+ # Returns nothing.
211
+ def attach
212
+ send_cmd("attach,#{Process.pid}")
213
+ if wait_for{ @attached == true }
214
+ STDERR.puts "*** attached to process #{pid}"
215
+ else
216
+ raise ArgumentError, 'process already being traced?'
217
+ end
218
+ end
219
+
201
220
  # Detach from the traced process.
202
221
  #
203
222
  # Returns nothing.
@@ -209,13 +228,16 @@ class RBTracer
209
228
 
210
229
  puts
211
230
 
212
- # drain queue
213
- 5.times do
214
- begin
215
- true while recv_cmd(false)
216
- rescue
217
- end
231
+ if wait_for{ @attached == false }
232
+ puts
233
+ STDERR.puts "*** detached from process #{pid}"
234
+ else
235
+ puts
236
+ STDERR.puts "*** could not detach cleanly from process #{pid}"
218
237
  end
238
+ rescue Errno::EINVAL, Errno::EIDRM
239
+ puts
240
+ STDERR.puts "*** process #{pid} is gone"
219
241
  end
220
242
 
221
243
  # Process events from the traced process.
@@ -224,26 +246,62 @@ class RBTracer
224
246
  def recv_loop
225
247
  while true
226
248
  # block until a message arrives
227
- lines = [recv_cmd]
249
+ process_line(recv_cmd)
228
250
 
229
- # check to see if there are more messages and pull them off
230
- # so the queue doesn't fill up in kernel land
231
- 25.times do
232
- break unless line = recv_cmd(false)
233
- lines << line
234
- end
251
+ # process any remaining messages
252
+ recv_lines
253
+ end
254
+ rescue Errno::EINVAL, Errno::EIDRM
255
+ # process went away
256
+ end
235
257
 
236
- lines.each do |line|
237
- process_line(line)
238
- end
258
+ # Process events from the traced process, without blocking if
259
+ # there is nothing to do. This is a useful way to drain the buffer
260
+ # so messages do not accumulate in kernel land.
261
+ #
262
+ # Returns nothing.
263
+ def recv_lines
264
+ 50.times do
265
+ break unless line = recv_cmd(false)
266
+ process_line(line)
239
267
  end
240
268
  end
241
269
 
242
270
  private
243
271
 
272
+ # Process incoming events until either a timeout or a condition becomes true.
273
+ #
274
+ # time - The Fixnum timeout in seconds.
275
+ # block - The Block that is checked every 50ms until it returns true.
276
+ #
277
+ # Returns true when the condition was met, or false on a timeout.
278
+ def wait_for(time=5)
279
+ wait = 0.05 # polling interval
280
+
281
+ (time/wait).to_i.times do
282
+ begin
283
+ recv_lines
284
+ sleep(wait)
285
+ time -= wait
286
+
287
+ return true if yield
288
+ rescue Interrupt
289
+ STDERR.puts "*** waiting to detach cleanly (#{time.to_i}s left)"
290
+ retry
291
+ end
292
+ end
293
+
294
+ false
295
+ end
296
+
244
297
  def send_cmd(msg)
245
- MsgQ::EventMsg.send_cmd(@qo, msg)
298
+ begin
299
+ MsgQ::EventMsg.send_cmd(@qo, msg)
300
+ rescue Errno::EINTR
301
+ retry
302
+ end
246
303
  Process.kill 'URG', @pid
304
+ recv_lines
247
305
  end
248
306
 
249
307
  def recv_cmd(block=true)
@@ -263,19 +321,45 @@ class RBTracer
263
321
  end
264
322
 
265
323
  def process_line(line)
266
- time, event, id, *args = line.strip.split(',')
324
+ time, event, _id, *args = line.strip.split(',')
267
325
  time = time.to_i
268
- id = id.to_i
326
+ id = _id.to_i
327
+
328
+ case event
329
+ when 'attached'
330
+ @attached = true
331
+ if id != Process.pid
332
+ STDERR.puts "*** process #{pid} is already being traced"
333
+ exit!(-1)
334
+ end
335
+ return
336
+
337
+ when 'detached'
338
+ @attached = false
339
+ return
340
+ end
341
+
342
+ if !@attached
343
+ STDERR.puts "*** got #{event} before attaching"
344
+ return
345
+ end
346
+
269
347
  tracer = @tracers[id] if id > -1
270
348
 
271
349
  case event
350
+ when 'mid'
351
+ @methods[id.to_i] = args.first
352
+
353
+ when 'klass'
354
+ @klasses[_id.to_i(16)] = args.first
355
+
272
356
  when 'add'
273
357
  if id == -1
274
358
  puts line
275
359
  else
276
- name = args.first
360
+ query = args.first
277
361
  @tracers.delete(id)
278
- @tracers[id][:name] = name
362
+ @tracers[id][:query] = query
279
363
  end
280
364
 
281
365
  when 'remove'
@@ -294,7 +378,8 @@ class RBTracer
294
378
  end
295
379
 
296
380
  when 'exprval'
297
- expr_id, val = *args
381
+ expr_id = args.shift
382
+ val = args.join(',')
298
383
  expr_id = expr_id.to_i
299
384
  expr = tracer[:exprs][expr_id]
300
385
 
@@ -311,8 +396,9 @@ class RBTracer
311
396
  when 'call','ccall'
312
397
  method, is_singleton, klass = *args
313
398
  is_singleton = (is_singleton == '1')
399
+ klass = @klasses[klass.to_i(16)]
314
400
  name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
315
- name += method
401
+ name += @methods[method.to_i] || '(unknown)'
316
402
 
317
403
  tracer[:times] << time
318
404
  tracer[:names] << name
@@ -331,6 +417,7 @@ class RBTracer
331
417
  print name
332
418
 
333
419
  @nesting += 1
420
+ @max_nesting = @nesting if @nesting > @max_nesting
334
421
  @last_tracer = tracer
335
422
  tracer[:last] = name
336
423
 
@@ -351,7 +438,12 @@ class RBTracer
351
438
  print name
352
439
  end
353
440
  print ' <%f>' % (diff/1_000_000.0) if @show_duration
354
- puts if @nesting == 0 and (tracer != @last_tracer || @last_tracer[:last] != name)
441
+
442
+ if @nesting == 0 and @max_nesting > 1
443
+ # unless tracer == @last_tracer and @last_tracer[:last] == name
444
+ puts
445
+ # end
446
+ end
355
447
  end
356
448
 
357
449
  tracer[:arglist] = false
@@ -362,8 +454,9 @@ class RBTracer
362
454
  diff = diff.to_i
363
455
 
364
456
  is_singleton = (is_singleton == '1')
457
+ klass = @klasses[klass.to_i(16)]
365
458
  name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
366
- name += method
459
+ name += @methods[method.to_i] || '(unknown)'
367
460
 
368
461
  print @prefix*nesting if nesting > 0
369
462
  print name
@@ -390,7 +483,7 @@ class RBTracer
390
483
  parser = Trollop::Parser.new do
391
484
  version <<-EOS
392
485
  rbtrace: like strace, but for ruby code
393
- version 0.2.7
486
+ version 0.2.8
394
487
  (c) 2011 Aman Gupta (tmm1)
395
488
  http://github.com/tmm1/rbtrace
396
489
  EOS
@@ -416,6 +509,10 @@ Tracers:
416
509
  rbtrace --slow=250 # trace method calls slower than 250ms
417
510
  rbtrace --methods a b c # trace calls to given methods
418
511
 
512
+ rbtrace -c io # trace common input/output functions
513
+ rbtrace -c eventmachine # trace common eventmachine functions
514
+ rbtrace -c my.tracer # trace all methods listed in my.tracer
515
+
419
516
  Method Selectors:
420
517
 
421
518
  sleep # any instance or class method named sleep
@@ -469,6 +566,10 @@ EOS
469
566
  :type => String,
470
567
  :short => '-o'
471
568
 
569
+ opt :append,
570
+ "append to output file instead of overwriting",
571
+ :short => '-a'
572
+
472
573
  opt :prefix,
473
574
  "prefix nested method calls with N spaces",
474
575
  :default => 2,
@@ -476,7 +577,7 @@ EOS
476
577
 
477
578
  opt :config,
478
579
  "config file",
479
- :type => String,
580
+ :type => :strings,
480
581
  :short => '-c'
481
582
  end
482
583
 
@@ -485,6 +586,10 @@ EOS
485
586
  parser.parse(ARGV)
486
587
  end
487
588
 
589
+ if out = opts[:output]
590
+ file = File.open(out, opts[:append] ? 'a+' : 'w')
591
+ end
592
+
488
593
  slow = nil
489
594
  firehose = nil
490
595
  methods = nil
@@ -499,15 +604,25 @@ EOS
499
604
  methods = opts[:methods]
500
605
 
501
606
  elsif opts[:config_given]
502
- if File.exists?(config = opts[:config])
503
- methods = []
504
- File.readlines(config).each do |line|
505
- next if line =~ /^#/
506
- methods << line.strip
607
+ methods = []
608
+
609
+ Array(opts[:config]).each do |config|
610
+ file = [
611
+ config,
612
+ File.expand_path("../../tracers/#{config}.tracer",__FILE__)
613
+ ].find{ |f| File.exists?(f) }
614
+
615
+ unless file
616
+ parser.die :config, '(file does not exist)'
507
617
  end
508
618
 
509
- else
510
- parser.die :config, "invalid config file"
619
+ File.readlines(file).each do |line|
620
+ line.strip!
621
+ next if line =~ /^#/
622
+ next if line.empty?
623
+
624
+ methods << line
625
+ end
511
626
  end
512
627
 
513
628
  else
@@ -520,7 +635,7 @@ EOS
520
635
  begin
521
636
  tracer = RBTracer.new(opts[:pid])
522
637
  rescue ArgumentError => e
523
- parser.die :pid, "invalid (#{e.message})"
638
+ parser.die :pid, "(#{e.message})"
524
639
  end
525
640
 
526
641
  if slow
@@ -531,8 +646,8 @@ EOS
531
646
  tracer.add(methods)
532
647
  end
533
648
 
534
- if out = opts[:output]
535
- tracer.out = File.open(out,'w')
649
+ if file
650
+ tracer.out = file
536
651
  end
537
652
 
538
653
  tracer.prefix = ' ' * opts[:prefix]
@@ -540,14 +655,12 @@ EOS
540
655
  tracer.show_time = opts[:start_time]
541
656
 
542
657
  begin
543
- STDERR.puts "*** attached to process #{tracer.pid}"
544
658
  tracer.recv_loop
545
659
  rescue Interrupt
546
660
  end
547
661
  ensure
548
662
  if tracer
549
663
  tracer.detach
550
- STDERR.puts "*** detached from process #{tracer.pid}"
551
664
  end
552
665
  end
553
666
  end
data/ext/rbtrace.c CHANGED
@@ -20,9 +20,11 @@
20
20
  #include <env.h>
21
21
  #include <intern.h>
22
22
  #include <node.h>
23
+ #include <st.h>
23
24
  #define rb_sourcefile() (ruby_current_node ? ruby_current_node->nd_file : 0)
24
25
  #define rb_sourceline() (ruby_current_node ? nd_line(ruby_current_node) : 0)
25
26
  #else
27
+ #include <ruby/st.h>
26
28
  // this is a nasty hack, and will probably break on anything except 1.9.2p136
27
29
  int rb_thread_method_id_and_class(void *th, ID *idp, VALUE *klassp);
28
30
  RUBY_EXTERN void *ruby_current_thread;
@@ -63,14 +65,17 @@ struct rbtracer_t {
63
65
  };
64
66
  typedef struct rbtracer_t rbtracer_t;
65
67
 
66
-
67
68
  struct event_msg {
68
69
  long mtype;
69
70
  char buf[BUF_SIZE];
70
71
  };
71
72
 
72
-
73
73
  static struct {
74
+ st_table *mid_tbl;
75
+ st_table *klass_tbl;
76
+
77
+ pid_t attached_pid;
78
+
74
79
  bool installed;
75
80
 
76
81
  bool firehose;
@@ -89,6 +94,11 @@ static struct {
89
94
  int mqi_id;
90
95
  }
91
96
  rbtracer = {
97
+ .mid_tbl = NULL,
98
+ .klass_tbl = NULL,
99
+
100
+ .attached_pid = 0,
101
+
92
102
  .installed = false,
93
103
 
94
104
  .firehose = false,
@@ -110,7 +120,7 @@ rbtracer = {
110
120
  if (false) {\
111
121
  fprintf(stderr, "%" PRIu64 "," format, usec, __VA_ARGS__);\
112
122
  fprintf(stderr, "\n");\
113
- } else if (rbtracer.mqo_id != -1) {\
123
+ } else if (rbtracer.mqo_id != -1 && rbtracer.attached_pid) {\
114
124
  struct event_msg msg;\
115
125
  int ret = -1, n = 0;\
116
126
  \
@@ -119,8 +129,9 @@ rbtracer = {
119
129
  \
120
130
  for (n=0; n<10 && ret==-1; n++)\
121
131
  ret = msgsnd(rbtracer.mqo_id, &msg, sizeof(msg)-sizeof(long), IPC_NOWAIT);\
122
- if (ret == -1) {\
123
- fprintf(stderr, "msgsnd(): %s\n", strerror(errno));\
132
+ \
133
+ if (ret == -1 && rbtracer.mqo_id != -1 && errno != EINVAL) {\
134
+ fprintf(stderr, "msgsnd(%d): %s\n", rbtracer.mqo_id, strerror(errno));\
124
135
  struct msqid_ds stat;\
125
136
  msgctl(rbtracer.mqo_id, IPC_STAT, &stat);\
126
137
  fprintf(stderr, "cbytes: %lu, qbytes: %lu, qnum: %lu\n", stat.msg_cbytes, stat.msg_qbytes, stat.msg_qnum);\
@@ -128,6 +139,34 @@ rbtracer = {
128
139
  }\
129
140
  } while (0)
130
141
 
142
+ static inline void
143
+ SEND_NAMES(ID mid, VALUE klass)
144
+ {
145
+ if (!rbtracer.mid_tbl)
146
+ rbtracer.mid_tbl = st_init_numtable();
147
+
148
+ if (!st_is_member(rbtracer.mid_tbl, mid)) {
149
+ st_insert(rbtracer.mid_tbl, (st_data_t)mid, (st_data_t)1);
150
+ SEND_EVENT(
151
+ "mid,%lu,%s",
152
+ mid,
153
+ rb_id2name(mid)
154
+ );
155
+ }
156
+
157
+ if (!rbtracer.klass_tbl)
158
+ rbtracer.klass_tbl = st_init_numtable();
159
+
160
+ if (!st_is_member(rbtracer.klass_tbl, klass)) {
161
+ st_insert(rbtracer.klass_tbl, (st_data_t)klass, (st_data_t)1);
162
+ SEND_EVENT(
163
+ "klass,%p,%s",
164
+ (void*)klass,
165
+ rb_class2name(klass)
166
+ );
167
+ }
168
+ }
169
+
131
170
  static void
132
171
  #ifdef RUBY_VM
133
172
  event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
@@ -191,14 +230,15 @@ event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
191
230
  }
192
231
 
193
232
  if (diff > rbtracer.threshold * 1e3) {
233
+ SEND_NAMES(mid, singleton ? self : klass);
194
234
  SEND_EVENT(
195
- "%s,-1,%" PRIu64 ",%d,%s,%d,%s",
235
+ "%s,-1,%" PRIu64 ",%d,%lu,%d,%p",
196
236
  event == RUBY_EVENT_RETURN ? "slow" : "cslow",
197
237
  diff,
198
238
  rbtracer.num_calls,
199
- rb_id2name(mid),
239
+ mid,
200
240
  singleton,
201
- klass ? rb_class2name(singleton ? self : klass) : ""
241
+ (void*)(singleton ? self : klass)
202
242
  );
203
243
  }
204
244
 
@@ -234,13 +274,14 @@ event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
234
274
  switch (event) {
235
275
  case RUBY_EVENT_CALL:
236
276
  case RUBY_EVENT_C_CALL:
277
+ SEND_NAMES(mid, singleton ? self : klass);
237
278
  SEND_EVENT(
238
- "%s,%d,%s,%d,%s",
279
+ "%s,%d,%lu,%d,%p",
239
280
  event == RUBY_EVENT_CALL ? "call" : "ccall",
240
281
  tracer ? tracer->id : 255, // hax
241
- rb_id2name(mid),
282
+ mid,
242
283
  singleton,
243
- klass ? rb_class2name(singleton ? self : klass) : ""
284
+ (void*)(singleton ? self : klass)
244
285
  );
245
286
 
246
287
  if (tracer && tracer->num_exprs) {
@@ -385,6 +426,14 @@ rbtracer_remove_all()
385
426
  rbtracer_remove(NULL, i);
386
427
  }
387
428
  }
429
+
430
+ if (rbtracer.mid_tbl)
431
+ st_free_table(rbtracer.mid_tbl);
432
+ rbtracer.mid_tbl = NULL;
433
+
434
+ if (rbtracer.klass_tbl)
435
+ st_free_table(rbtracer.klass_tbl);
436
+ rbtracer.klass_tbl = NULL;
388
437
  }
389
438
 
390
439
  static int
@@ -625,7 +674,27 @@ sigurg(int signal)
625
674
  } else if (0 == strncmp("unwatch", msg.buf, 7)) {
626
675
  rbtracer_unwatch();
627
676
 
677
+ } else if (0 == strncmp("attach,", msg.buf, 7)) {
678
+ pid_t pid = 0;
679
+
680
+ query = msg.buf + 7;
681
+ if (query && *query)
682
+ pid = (pid_t)atoi(query);
683
+
684
+ if (pid && rbtracer.attached_pid == 0)
685
+ rbtracer.attached_pid = pid;
686
+
687
+ SEND_EVENT(
688
+ "attached,%u",
689
+ rbtracer.attached_pid
690
+ );
691
+
628
692
  } else if (0 == strncmp("detach", msg.buf, 6)) {
693
+ SEND_EVENT(
694
+ "detached,%u",
695
+ rbtracer.attached_pid
696
+ );
697
+ rbtracer.attached_pid = 0;
629
698
  rbtracer_remove_all();
630
699
 
631
700
  }
data/rbtrace.gemspec CHANGED
@@ -1,13 +1,13 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rbtrace'
3
- s.version = '0.2.7'
3
+ s.version = '0.2.8'
4
4
  s.homepage = 'http://github.com/tmm1/rbtrace'
5
5
 
6
- s.authors = "Aman Gupta"
7
- s.email = "aman@tmm1.net"
6
+ s.authors = 'Aman Gupta'
7
+ s.email = 'aman@tmm1.net'
8
8
 
9
9
  s.files = `git ls-files`.split("\n")
10
- s.extensions = "ext/extconf.rb"
10
+ s.extensions = 'ext/extconf.rb'
11
11
 
12
12
  s.bindir = 'bin'
13
13
  s.executables << 'rbtrace'
@@ -0,0 +1 @@
1
+ ActiveRecord::ConnectionAdapters::MysqlAdapter#execute(sql)
@@ -0,0 +1,27 @@
1
+ # incoming events
2
+ post_init(@signature)
3
+ connection_completed(@signature)
4
+ receive_data(@signature)
5
+ unbind(@signature)
6
+
7
+ # connection methods
8
+ EM::Connection#send_data(@signature, data.size, data[0..5])
9
+ EM::Connection#send_datagram(@signature, data.size, data[0..5])
10
+ EM::Connection#start_tls(@signature, args)
11
+ EM::Connection#close_connection(@signature, after_writing)
12
+ EM::Connection#proxy_incoming_to(@signature, conn, bufsize)
13
+ EM::Connection#(@signature)
14
+
15
+ # timers
16
+ EM.add_periodic_timer(args, block)
17
+ EM.add_timer(args, block)
18
+ EM.cancel_timer(timer_or_sig,@timers.size)
19
+
20
+ # event loop
21
+ EM.stop
22
+
23
+ # connections
24
+ EM.start_server(server, port, handler)
25
+ EM.stop_server
26
+ EM.connect(server, port, handler)
27
+ EM.reconnect(server, port, handler)
data/tracers/io.tracer ADDED
@@ -0,0 +1,4 @@
1
+ IO.select
2
+ IO#
3
+ TCPSocket#
4
+ TCPServer#
@@ -0,0 +1 @@
1
+ Redis::Client#process(commands)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbtrace
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 7
10
- version: 0.2.7
9
+ - 8
10
+ version: 0.2.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aman Gupta
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-16 00:00:00 -08:00
18
+ date: 2011-02-17 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -70,6 +70,10 @@ files:
70
70
  - rbtrace.gemspec
71
71
  - server.rb
72
72
  - test.sh
73
+ - tracers/activerecord.tracer
74
+ - tracers/eventmachine.tracer
75
+ - tracers/io.tracer
76
+ - tracers/redis.tracer
73
77
  has_rdoc: true
74
78
  homepage: http://github.com/tmm1/rbtrace
75
79
  licenses: []