rbtrace 0.2.7 → 0.2.8

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 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: []