XRay 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/README +34 -0
- data/lib/xray/dtrace/rails/action_controller_tracing_extension.rb +22 -0
- data/lib/xray/dtrace/rails/active_record_connection_tracing_extension.rb +15 -0
- data/lib/xray/dtrace/rails/enable_tracing.rb +25 -0
- data/lib/xray/dtrace/tracer.rb +111 -0
- data/lib/xray/enable_thread_aware_dispatcher.rb +1 -0
- data/lib/xray/rails_stack_signal_handler.rb +15 -0
- data/lib/xray/signal_handler.rb +26 -0
- data/lib/xray/thread_aware_dispatcher.rb +27 -0
- data/lib/xray/xray.rb +1 -0
- data/test/all_tests.rb +1 -0
- data/test/functional/dtrace/tracer_test.rb +54 -0
- data/test/functional/tracer_script.rb +18 -0
- data/test/test_helper.rb +10 -0
- data/test/unit/thread_aware_dispatcher_test.rb +51 -0
- data/test/unit/xray/dtrace/tracer_test.rb +54 -0
- metadata +72 -0
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
XRay provides a lightweight yet powerful toolbox for troubleshooting Ruby
|
2
|
+
applications when things stop making sense. Includes GDB and DTrace tooling.
|
3
|
+
|
4
|
+
*** GDB ***
|
5
|
+
|
6
|
+
|
7
|
+
Copy +gdb_macros+ file provided in the gem as your ~/.gdbinit file.
|
8
|
+
|
9
|
+
*** Fire DTrace Application Probes ***
|
10
|
+
|
11
|
+
See XRay::DTrace::Tracer
|
12
|
+
|
13
|
+
*** Out-of-the-box Rails DTrace Instrumentation ***
|
14
|
+
|
15
|
+
You are one require away from triggering automatically DTrace events for
|
16
|
+
Rails requests, database access and template rendering. As simple as
|
17
|
+
|
18
|
+
# environment.rb
|
19
|
+
Rails::Initializer.run do |config|
|
20
|
+
|
21
|
+
...
|
22
|
+
|
23
|
+
config.after_initialize do
|
24
|
+
require "xray/dtrace/rails/enable_tracing"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
See
|
29
|
+
* lib/xray/dtrace/railsenable_tracing.rb
|
30
|
+
* lib/xray/dtrace/action_controller_tracing_extension.rb
|
31
|
+
* lib/xray/dtrace/active_record_tracing_extension.rb
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# Decorate ActionController::Base with request tracing
|
3
|
+
#
|
4
|
+
ActionController::Base.class_eval do
|
5
|
+
include XRay::DTrace::Tracer
|
6
|
+
|
7
|
+
def perform_action_with_tracing
|
8
|
+
firing('request', "#{self.class.to_s}##{action_name.to_s}") do
|
9
|
+
perform_action_without_tracing
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_with_tracing(options = nil, deprecated_status = nil, &block)
|
14
|
+
firing('render', options.to_s) do
|
15
|
+
render_without_tracing options, deprecated_status
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method_chain :perform_action, :tracing
|
20
|
+
alias_method_chain :render, :tracing
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Decorate ActiveRecord connection with DB tracing
|
3
|
+
#
|
4
|
+
ActiveRecord::Base.connection.class.class_eval do
|
5
|
+
include XRay::DTrace::Tracer
|
6
|
+
|
7
|
+
def execute_with_tracing(sql, name=nil)
|
8
|
+
firing('db', sql) do
|
9
|
+
execute_without_tracing sql, name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method_chain :execute, :tracing
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Require this file to enable out the box DTrace tracing
|
2
|
+
# for your Rails application
|
3
|
+
#
|
4
|
+
# You typically change your environment.rb to require it after config
|
5
|
+
# initialization:
|
6
|
+
#
|
7
|
+
# Rails::Initializer.run do |config|
|
8
|
+
#
|
9
|
+
# ...
|
10
|
+
#
|
11
|
+
# config.after_initialize do
|
12
|
+
# require "xray/dtrace/rails/enable_tracing"
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
|
16
|
+
require 'xray/dtrace/tracer'
|
17
|
+
if Object.const_defined? :ActionController
|
18
|
+
puts "Enabling controller tracing"
|
19
|
+
require "xray/dtrace/rails/action_controller_tracing_extension"
|
20
|
+
end
|
21
|
+
|
22
|
+
if Object.const_defined? :ActiveRecord
|
23
|
+
puts "Enabling DB tracing"
|
24
|
+
require "xray/dtrace/rails/active_record_connection_tracing_extension"
|
25
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module XRay
|
2
|
+
module DTrace
|
3
|
+
|
4
|
+
# Ruby module to fire application-level Dtrace events (using ruby-probe).
|
5
|
+
#
|
6
|
+
# This module provide a convenient and unified API abstracting
|
7
|
+
# different tracing implementations in Leopard Ruby VM (by Apple)
|
8
|
+
# and in the one provided by Joyent (https://dev.joyent.com/projects/ruby-dtrace).
|
9
|
+
# This module also provides a NOOP implementation for Ruby VMs with
|
10
|
+
# no DTrace support: So you can use the exact same code while developing
|
11
|
+
# on Linux and deploying on Solaris for instance.
|
12
|
+
module Tracer
|
13
|
+
|
14
|
+
if Object.const_defined?(:DTracer) ### Leopard tracer ###
|
15
|
+
|
16
|
+
# Fire an application-level probe using ruby-probe.
|
17
|
+
#
|
18
|
+
# The first argument passed will be passed to the D script as arg0 for
|
19
|
+
# the ruby-probe probe. This is conventionally a probe name.
|
20
|
+
#
|
21
|
+
# The second argument is optional and can be used to pass additional
|
22
|
+
# data into the D script as arg1.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# XRay::DTrace::Tracer.fire('service-start', "order processing")
|
27
|
+
#
|
28
|
+
# XRay::DTrace::Tracer.fire('service-stop')
|
29
|
+
#
|
30
|
+
def fire(name, data = nil)
|
31
|
+
DTracer.fire(name, data)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Use ruby-probe to fire 2 application-level probes before and after
|
35
|
+
# evaling a block .
|
36
|
+
#
|
37
|
+
# The first argument passed will be passed to the D script as arg0 for
|
38
|
+
# the ruby-probe probe. This is conventionally a probe base name. The
|
39
|
+
# probes which fire before and after the block runs will have
|
40
|
+
# "-start" and "-end" appended, respectively.
|
41
|
+
#
|
42
|
+
# The second argument is optional and can be used to pass additional
|
43
|
+
# data into the D script as arg1.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# XRay::DTrace::Tracer.fire('service-start', "order processing")
|
48
|
+
#
|
49
|
+
# XRay::DTrace::Tracer.firing('db-query', "select * from dual;") do
|
50
|
+
# ActiveRecord::Base.execute("select * from dual;")
|
51
|
+
#
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Will:
|
55
|
+
# 1. Fire a probe with arg0 set to "db-query-start", and arg1 set
|
56
|
+
# to the sql query.
|
57
|
+
#
|
58
|
+
# 2. Run the block and execute the SQL.
|
59
|
+
#
|
60
|
+
# 3. Fire a probe with arg0 set to "db-query-start".
|
61
|
+
def firing(name, data = nil)
|
62
|
+
fire(name + "-start", data)
|
63
|
+
result = yield
|
64
|
+
fire(name + "-end", data)
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns true if ruby-probe probes are enables
|
69
|
+
# (application-level probes for Ruby).
|
70
|
+
def enabled?
|
71
|
+
DTracer.enabled?
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
elsif Object.const_defined?(:Tracer) && Tracer.methods.include?(:fire) ### Joyent Tracer ##
|
76
|
+
|
77
|
+
raise "got here"
|
78
|
+
|
79
|
+
def fire(name, data = nil) #:nodoc: all
|
80
|
+
|
81
|
+
::Tracer.fire(name, data)
|
82
|
+
end
|
83
|
+
|
84
|
+
def firing(name, data = nil) #:nodoc: all
|
85
|
+
::Tracer.fire(name, data)
|
86
|
+
end
|
87
|
+
|
88
|
+
def enabled? #:nodoc: all
|
89
|
+
STDERR.puts "WARNING: XRay::DTrace.Tracer.enabled? does not work with Joyent Tracer"
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
else ### No ruby-probe support ###
|
94
|
+
|
95
|
+
def fire(name, data = nil) #:nodoc: all
|
96
|
+
end
|
97
|
+
|
98
|
+
def firing(name, data = nil) #:nodoc: all
|
99
|
+
yield
|
100
|
+
end
|
101
|
+
|
102
|
+
def enabled? #:nodoc: all
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dispatcher.send :include, XRay::ThreadAwareDispatcher
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Install a signal handler dumping Rails stack by raising an exception
|
3
|
+
#
|
4
|
+
# Trigger it with: kill -QUIT <pid>
|
5
|
+
#
|
6
|
+
|
7
|
+
trap "QUIT" do
|
8
|
+
STDERR.puts "=============== XRay - Raising exception in Rails thread ==============="
|
9
|
+
if Dispatcher.thread_in_dispatch
|
10
|
+
Dispatcher.thread_in_dispatch.raise "XRay - Forced exception to Rails stack trace"
|
11
|
+
else
|
12
|
+
STDERR.puts "No Rails thread in dispatch"
|
13
|
+
end
|
14
|
+
STDERR.puts "=============== XRay - Done ==============="
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Install a signal handler dumping current thread stack
|
3
|
+
#
|
4
|
+
# Trigger it with: kill -QUIT <pid>
|
5
|
+
#
|
6
|
+
module XRay
|
7
|
+
|
8
|
+
def self.dump_threads
|
9
|
+
STDERR.puts "=============== XRay Inspector ==============="
|
10
|
+
STDERR.puts "Current Thread\n "
|
11
|
+
STDERR.puts caller.join("\n \_ ")
|
12
|
+
STDERR.puts Thread.current.xray_backtrace.join("\n \_ ")
|
13
|
+
# STDERR.puts "----------------------------------------------"
|
14
|
+
# Thread.list.each_with_index do |t,i|
|
15
|
+
# STDERR.puts "Dumping Thread #{i}\n "
|
16
|
+
# t.xray_backtrace.join("\n \_ ")
|
17
|
+
# end
|
18
|
+
STDERR.puts "=============================================="
|
19
|
+
STDERR.flush
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
trap "QUIT" do
|
25
|
+
XRay.dump_threads
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module XRay
|
2
|
+
|
3
|
+
module ThreadAwareDispatcher
|
4
|
+
|
5
|
+
# Intercept dispatch with our own method when the module is included.
|
6
|
+
def self.included(target)
|
7
|
+
class << target
|
8
|
+
attr_reader :thread_in_dispatch
|
9
|
+
|
10
|
+
alias actual_dispatch dispatch
|
11
|
+
|
12
|
+
# Capture as an instance variable the current
|
13
|
+
# thread -- which is processing current Rails request --
|
14
|
+
# and do the actual dispatch.
|
15
|
+
def dispatch(*args)
|
16
|
+
@thread_in_dispatch = Thread.current
|
17
|
+
actual_dispatch *args
|
18
|
+
ensure
|
19
|
+
@thread_in_dispatch = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/xray/xray.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'xray/thread_aware_dispatcher'
|
data/test/all_tests.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{File.dirname __FILE__}/**/*_test.rb"].each { |test_case| require test_case }
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(__FILE__ + '/../../../test_helper')
|
2
|
+
|
3
|
+
functional_tests do
|
4
|
+
|
5
|
+
test "fire a probe with no data and no block" do
|
6
|
+
aClass = Class.new do
|
7
|
+
include XRay::DTrace::Tracer
|
8
|
+
end
|
9
|
+
|
10
|
+
aClass.new.fire "a name"
|
11
|
+
end
|
12
|
+
|
13
|
+
test "fire a probe with data and no block" do
|
14
|
+
aClass = Class.new do
|
15
|
+
include XRay::DTrace::Tracer
|
16
|
+
end
|
17
|
+
|
18
|
+
aClass.new.fire "a name", "some data"
|
19
|
+
end
|
20
|
+
|
21
|
+
test "Can check whether ruby-probe is enabled" do
|
22
|
+
aClass = Class.new do
|
23
|
+
include XRay::DTrace::Tracer
|
24
|
+
end
|
25
|
+
|
26
|
+
assert [true, false].include?(aClass.new.enabled?)
|
27
|
+
end
|
28
|
+
|
29
|
+
test "fire a probe with block and no data" do
|
30
|
+
anObject = Class.new do
|
31
|
+
include XRay::DTrace::Tracer
|
32
|
+
end.new
|
33
|
+
|
34
|
+
result = anObject.firing("a-name") do
|
35
|
+
:expected_result
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_equal :expected_result, result
|
39
|
+
end
|
40
|
+
|
41
|
+
test "fire a probe with block and data" do
|
42
|
+
anObject = Class.new do
|
43
|
+
include XRay::DTrace::Tracer
|
44
|
+
end.new
|
45
|
+
|
46
|
+
result = anObject.firing("a-name", "some data") do
|
47
|
+
:expected_result
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal :expected_result, result
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../../lib'
|
2
|
+
require 'xray/dtrace/tracer'
|
3
|
+
|
4
|
+
class Service
|
5
|
+
include XRay::DTrace::Tracer
|
6
|
+
|
7
|
+
def process
|
8
|
+
puts "Processing new request"
|
9
|
+
firing "my-service", "a sql query" do
|
10
|
+
sleep 2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
loop do
|
17
|
+
Service.new.process
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path(__FILE__ + '/../../test_helper')
|
2
|
+
|
3
|
+
unit_tests do
|
4
|
+
|
5
|
+
test "thread aware dispatcher returns original dispath result" do
|
6
|
+
dispatcher = Class.new
|
7
|
+
dispatcher.expects(:dispatch).with(:the_args).returns(:the_result)
|
8
|
+
dispatcher.send :include, XRay::ThreadAwareDispatcher
|
9
|
+
dispatcher.dispatch(:the_args)
|
10
|
+
end
|
11
|
+
|
12
|
+
test "thread aware dispatcher adds accessor for thread in dispatch" do
|
13
|
+
dispatcher = Class.new
|
14
|
+
dispatcher.stubs(:dispatch)
|
15
|
+
dispatcher.send :include, XRay::ThreadAwareDispatcher
|
16
|
+
dispatcher.thread_in_dispatch
|
17
|
+
end
|
18
|
+
|
19
|
+
test "thread in dispatch is nil when not in dispatch" do
|
20
|
+
dispatcher = Class.new
|
21
|
+
dispatcher.stubs(:dispatch)
|
22
|
+
dispatcher.send :include, XRay::ThreadAwareDispatcher
|
23
|
+
assert_nil dispatcher.thread_in_dispatch
|
24
|
+
end
|
25
|
+
|
26
|
+
test "thread in dispatch is nil when dispatch raises" do
|
27
|
+
begin
|
28
|
+
dispatcher = Class.new
|
29
|
+
dispatcher.expects(:dispatch).raises(StandardError.new("Fake Problem"))
|
30
|
+
dispatcher.send :include, XRay::ThreadAwareDispatcher
|
31
|
+
dispatcher.dispatch
|
32
|
+
flunk "Should relay exception"
|
33
|
+
rescue StandardError
|
34
|
+
assert_nil dispatcher.thread_in_dispatch
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test "thread in dispatch is set to current thread when in dispatch" do
|
39
|
+
dispatcher = Class.new do
|
40
|
+
extend Test::Unit::Assertions
|
41
|
+
|
42
|
+
def self.dispatch(*args)
|
43
|
+
assert_equal Thread.current, thread_in_dispatch
|
44
|
+
end
|
45
|
+
|
46
|
+
include XRay::ThreadAwareDispatcher
|
47
|
+
end
|
48
|
+
dispatcher.dispatch
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(__FILE__ + '/../../../../test_helper')
|
2
|
+
|
3
|
+
unit_tests do
|
4
|
+
|
5
|
+
test "fire a probe with no data and no block" do
|
6
|
+
aClass = Class.new do
|
7
|
+
include XRay::DTrace::Tracer
|
8
|
+
end
|
9
|
+
|
10
|
+
aClass.new.fire "a name"
|
11
|
+
end
|
12
|
+
|
13
|
+
test "fire a probe with data and no block" do
|
14
|
+
aClass = Class.new do
|
15
|
+
include XRay::DTrace::Tracer
|
16
|
+
end
|
17
|
+
|
18
|
+
aClass.new.fire "a name", "some data"
|
19
|
+
end
|
20
|
+
|
21
|
+
test "Can check whether ruby-probe is enabled" do
|
22
|
+
aClass = Class.new do
|
23
|
+
include XRay::DTrace::Tracer
|
24
|
+
end
|
25
|
+
|
26
|
+
assert [true, false].include?(aClass.new.enabled?)
|
27
|
+
end
|
28
|
+
|
29
|
+
test "fire a probe with block and no data" do
|
30
|
+
anObject = Class.new do
|
31
|
+
include XRay::DTrace::Tracer
|
32
|
+
end.new
|
33
|
+
|
34
|
+
result = anObject.firing("a-name") do
|
35
|
+
:expected_result
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_equal :expected_result, result
|
39
|
+
end
|
40
|
+
|
41
|
+
test "fire a probe with block and data" do
|
42
|
+
anObject = Class.new do
|
43
|
+
include XRay::DTrace::Tracer
|
44
|
+
end.new
|
45
|
+
|
46
|
+
result = anObject.firing("a-name", "some data") do
|
47
|
+
:expected_result
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal :expected_result, result
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: XRay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philippe Hanrigou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-03-28 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: xray-developer@rubyforge.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- lib/xray/dtrace/rails/action_controller_tracing_extension.rb
|
26
|
+
- lib/xray/dtrace/rails/active_record_connection_tracing_extension.rb
|
27
|
+
- lib/xray/dtrace/rails/enable_tracing.rb
|
28
|
+
- lib/xray/dtrace/tracer.rb
|
29
|
+
- lib/xray/enable_thread_aware_dispatcher.rb
|
30
|
+
- lib/xray/rails_stack_signal_handler.rb
|
31
|
+
- lib/xray/signal_handler.rb
|
32
|
+
- lib/xray/thread_aware_dispatcher.rb
|
33
|
+
- lib/xray/xray.rb
|
34
|
+
- test/all_tests.rb
|
35
|
+
- test/functional/dtrace/tracer_test.rb
|
36
|
+
- test/functional/tracer_script.rb
|
37
|
+
- test/test_helper.rb
|
38
|
+
- test/unit/thread_aware_dispatcher_test.rb
|
39
|
+
- test/unit/xray/dtrace/tracer_test.rb
|
40
|
+
- README
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://xray.rubyforge.com
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --title
|
46
|
+
- XRay
|
47
|
+
- --main
|
48
|
+
- README
|
49
|
+
- --line-numbers
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: xray
|
67
|
+
rubygems_version: 1.0.1
|
68
|
+
signing_key:
|
69
|
+
specification_version: 2
|
70
|
+
summary: Dump backtrace for all threads.
|
71
|
+
test_files:
|
72
|
+
- test/all_tests.rb
|