xray 1.1

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.
Files changed (30) hide show
  1. data/README +65 -0
  2. data/bin/install_dtrace_on_ubuntu +30 -0
  3. data/bin/xray_profile_ruby_function_calls.d +54 -0
  4. data/bin/xray_top_10_busiest_code_path_for_process.d +12 -0
  5. data/bin/xray_top_10_busiest_code_path_on_system.d +35 -0
  6. data/bin/xray_trace_all_custom_ruby_probes.d +25 -0
  7. data/bin/xray_trace_all_ruby_probes.d +20 -0
  8. data/bin/xray_trace_memcached.d +20 -0
  9. data/bin/xray_trace_mysql.d +30 -0
  10. data/bin/xray_trace_rails_response_times.d +114 -0
  11. data/lib/xray.rb +2 -0
  12. data/lib/xray/dtrace/rails/action_controller_tracing_extension.rb +22 -0
  13. data/lib/xray/dtrace/rails/active_record_connection_tracing_extension.rb +15 -0
  14. data/lib/xray/dtrace/rails/enable_tracing.rb +25 -0
  15. data/lib/xray/dtrace/tracer.rb +86 -0
  16. data/lib/xray/dtrace/tracer/joyent.rb +25 -0
  17. data/lib/xray/dtrace/tracer/leopard.rb +28 -0
  18. data/lib/xray/dtrace/usdt/provider_extensions.rb +45 -0
  19. data/lib/xray/thread_dump_signal_handler.rb +31 -0
  20. data/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.6.diff +229 -0
  21. data/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.7.diff +229 -0
  22. data/patches_for_mri/patch-for-dtrace-instrumentation-of-matz-1.8.6-p114.diff +522 -0
  23. data/patches_for_mri/patch-for-joyent-mri-1.8.6-on-mac-os-x-leopard.diff +20645 -0
  24. data/rails/init.rb +2 -0
  25. data/test/all_tests.rb +1 -0
  26. data/test/functional/dtrace/tracer_test.rb +54 -0
  27. data/test/functional/tracer_script.rb +18 -0
  28. data/test/test_helper.rb +9 -0
  29. data/test/unit/xray/dtrace/tracer_test.rb +54 -0
  30. metadata +84 -0
@@ -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 File.expand_path(File.dirname(__FILE__) + '/../tracer')
17
+ if Object.const_defined? :ActionController
18
+ puts "XRay: Enabling Rails Controller Tracing"
19
+ require File.expand_path(File.dirname(__FILE__) + "/../rails/action_controller_tracing_extension")
20
+ end
21
+
22
+ if Object.const_defined? :ActiveRecord
23
+ puts "XRay: Enabling Rails DB Tracing"
24
+ require File.expand_path(File.dirname(__FILE__) + "/../rails/active_record_connection_tracing_extension")
25
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/tracer/leopard.rb'
2
+ require File.dirname(__FILE__) + '/tracer/joyent.rb'
3
+
4
+ module XRay
5
+ module DTrace
6
+
7
+ # Ruby module to fire application-level Dtrace events (using ruby-probe).
8
+ #
9
+ # This module provide a convenient and unified API abstracting
10
+ # different tracing implementations in Leopard Ruby VM (by Apple)
11
+ # and in the one provided by Joyent (https://dev.joyent.com/projects/ruby-dtrace).
12
+ # This module also provides a NOOP implementation for Ruby VMs with
13
+ # no DTrace support: So you can use the exact same code while developing
14
+ # on Linux and deploying on Solaris for instance.
15
+ module Tracer
16
+
17
+ # Fire an application-level probe using ruby-probe.
18
+ #
19
+ # The first argument passed will be passed to the D script as arg0 for
20
+ # the ruby-probe probe. This is conventionally a probe name.
21
+ #
22
+ # The second argument is optional and can be used to pass additional
23
+ # data into the D script as arg1.
24
+ #
25
+ # Example:
26
+ #
27
+ # XRay::DTrace::Tracer.fire('service-start', "order processing")
28
+ #
29
+ # XRay::DTrace::Tracer.fire('service-stop')
30
+ #
31
+ def fire(name, data = nil)
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. The first argument is conventionally a
39
+ # probe base name. The probes which fire before and after the block
40
+ # runs will have "-start" and "-end" appended to the probe base name,
41
+ # respectively.
42
+ #
43
+ # The second argument is optional and can be used to pass additional
44
+ # data into the D script as arg1.
45
+ #
46
+ # Example:
47
+ #
48
+ # XRay::DTrace::Tracer.firing('db-query', "select * from dual;") do
49
+ # ActiveRecord::Base.execute("select * from dual;")
50
+ #
51
+ # end
52
+ #
53
+ # Will:
54
+ # 1. Fire a probe with arg0 set to "db-query-start", and arg1 set
55
+ # to the sql query.
56
+ #
57
+ # 2. Run the block and execute the SQL.
58
+ #
59
+ # 3. Fire a probe with arg0 set to "db-query-start".
60
+ def firing(name, data = nil)
61
+ yield
62
+ end
63
+
64
+ # Returns true if ruby-probe probes are enabled.
65
+ # (application-level probes for Ruby).
66
+ def enabled?
67
+ false
68
+ end
69
+
70
+ end
71
+
72
+ if Object.const_defined?(:DTracer)
73
+ actual_tracer = Tracer::Leopard
74
+ send :remove_const, :Tracer
75
+ Tracer = actual_tracer
76
+ elsif Object.const_defined?(:Tracer) && Tracer.methods.include?(:fire)
77
+ actual_tracer = Tracer::Joyent
78
+ send :remove_const, :Tracer
79
+ Tracer = actual_tracer
80
+ else
81
+ # No DTrace support, keep the NOOP implementation
82
+ end
83
+
84
+ end
85
+ end
86
+
@@ -0,0 +1,25 @@
1
+ module XRay
2
+ module DTrace
3
+ module Tracer
4
+
5
+ # Wrapper around OS X DTracer exposing a custom API and namespace.
6
+ module Joyent
7
+
8
+ def fire(name, data = nil) #:nodoc: all
9
+ ::Tracer.fire(name, data)
10
+ end
11
+
12
+ def firing(name, data = nil) #:nodoc: all
13
+ ::Tracer.fire(name, data)
14
+ end
15
+
16
+ def enabled? #:nodoc: all
17
+ STDERR.puts "WARNING: XRay::DTrace.Tracer.enabled? does not work with Joyent Tracer"
18
+ false
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ module XRay
2
+ module DTrace
3
+ module Tracer
4
+
5
+ # Wrapper around OS X DTracer exposing a custom API and namespace.
6
+ module Leopard
7
+
8
+ def fire(name, data = nil)
9
+ DTracer.fire(name, data)
10
+ end
11
+
12
+ def firing(name, data = nil)
13
+ fire(name + "-start", data)
14
+ result = yield
15
+ fire(name + "-end", data)
16
+ result
17
+ end
18
+
19
+ def enabled?
20
+ DTracer.enabled?
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # Extend ruby-dtrace API with a more convenient one to fire custom USDT
3
+ # probes
4
+ #
5
+
6
+ require "rubygems"
7
+ require 'dtrace/provider'
8
+
9
+ class Dtrace
10
+
11
+ class Provider
12
+
13
+ alias :original_load :load
14
+ def load
15
+ provider_module = original_load
16
+
17
+ xray_extension_module = Module.new do
18
+ @@provider_module = provider_module
19
+
20
+ def firing(function_prefix, *args)
21
+ @@provider_module.send :"#{function_prefix}_start" do |probe|
22
+ probe.fire(*args)
23
+ end
24
+ result = yield
25
+ @@provider_module.send :"#{function_prefix}_end" do |probe|
26
+ probe.fire(*args)
27
+ end
28
+ result
29
+ end
30
+
31
+ def fire(function_name, *args)
32
+ @@provider_module.send function_name do |p|
33
+ p.fire(*args)
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ provider_module.const_set :XRay, xray_extension_module
40
+ provider_module
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Install a signal handler to dump backtraces for all threads
3
+ #
4
+ # Trigger it with: kill -QUIT <pid>
5
+ #
6
+ trap "QUIT" do
7
+ if Kernel.respond_to? :caller_for_all_threads
8
+ STDERR.puts "\n=============== XRay - Thread Dump ==============="
9
+ caller_for_all_threads.each_pair do |thread, stack|
10
+ thread_description = thread.inspect
11
+ thread_description << " [main]" if thread == Thread.main
12
+ thread_description << " [current]" if thread == Thread.current
13
+ thread_description << " alive=#{thread.alive?}"
14
+ thread_description << " priority=#{thread.priority}"
15
+ thread_separator = "-" * 78
16
+
17
+ full_description = "\n#{thread_separator}\n"
18
+ full_description << thread_description
19
+ full_description << "\n#{thread_separator}\n"
20
+ full_description << " #{stack.join("\n \\_ ")}\n"
21
+
22
+ # Single puts to avoid interleaved output
23
+ STDERR.puts full_description
24
+ end
25
+ else
26
+ STDERR.puts "=============== XRay - Current Thread Backtrace ==============="
27
+ STDERR.puts "Current thread : #{Thread.inspect}"
28
+ STDERR.puts caller.join("\n \\_ ")
29
+ end
30
+ STDERR.puts "\n=============== XRay - Done ===============\n"
31
+ end
@@ -0,0 +1,229 @@
1
+ Index: test/callerforallthreads/test_caller_for_each_thread.rb
2
+ ===================================================================
3
+ --- test/callerforallthreads/test_caller_for_each_thread.rb (revision 0)
4
+ +++ test/callerforallthreads/test_caller_for_each_thread.rb (revision 0)
5
+ @@ -0,0 +1,95 @@
6
+ +# -*- ruby-indent-level: 4 -*-
7
+ +require 'thread'
8
+ +require 'test/unit'
9
+ +
10
+ +class AClassWithNestedmethods
11
+ +
12
+ + def an_ultra_nested_method(skip)
13
+ + caller_for_all_threads skip
14
+ + end
15
+ +
16
+ + def a_nested_method(skip)
17
+ + an_ultra_nested_method skip
18
+ + end
19
+ +
20
+ + def a_method(skip=0)
21
+ + a_nested_method skip
22
+ + end
23
+ +
24
+ +end
25
+ +
26
+ +class CallerForEachThreadTest < Test::Unit::TestCase
27
+ +
28
+ + def testCollectMeaningfulBacktraceForASingleThread
29
+ + backtraces = AClassWithNestedmethods.new.a_method
30
+ + backtrace = backtraces[Thread.current]
31
+ + assert_not_nil backtrace
32
+ + assert_equal __FILE__ + ":8:in `an_ultra_nested_method'", backtrace[0]
33
+ + assert_equal __FILE__ + ":12:in `a_nested_method'", backtrace[1]
34
+ + assert_equal __FILE__ + ":16:in `a_method'", backtrace[2]
35
+ + assert_equal __FILE__ + ":24:in `testCollectMeaningfulBacktraceForASingleThread'",
36
+ + backtrace[3]
37
+ + end
38
+ +
39
+ + def testCanSkipFirstStackEntries
40
+ + backtraces = AClassWithNestedmethods.new.a_method 2
41
+ + backtrace = backtraces[Thread.current]
42
+ + assert_not_nil backtrace
43
+ + assert_equal __FILE__ + ":16:in `a_method'", backtrace[0]
44
+ + assert_equal __FILE__ + ":35:in `testCanSkipFirstStackEntries'",
45
+ + backtrace[1]
46
+ + end
47
+ +
48
+ + def testCollectMeaningfulBacktraceForMultipleThreads
49
+ + first_thread = Thread.new do
50
+ + loop do
51
+ + Thread.pass
52
+ + sleep 1
53
+ + end
54
+ + end
55
+ +
56
+ + second_thread = Thread.new do
57
+ + loop do
58
+ + Thread.pass
59
+ + sleep 1
60
+ + end
61
+ + end
62
+ +
63
+ + backtraces = AClassWithNestedmethods.new.a_method
64
+ +
65
+ + backtrace = backtraces[Thread.current]
66
+ + assert_not_nil backtrace
67
+ + assert_match __FILE__ + ":8:in `an_ultra_nested_method'", backtrace[0]
68
+ + assert_match __FILE__ + ":12:in `a_nested_method'", backtrace[1]
69
+ + assert_equal __FILE__ + ":16:in `a_method'", backtrace[2]
70
+ + assert_equal __FILE__ + ":58:in `testCollectMeaningfulBacktraceForMultipleThreads'",
71
+ + backtrace[3]
72
+ +
73
+ + backtrace = backtraces[first_thread]
74
+ + assert_not_nil backtrace
75
+ + assert_equal __FILE__ + ":47:in `testCollectMeaningfulBacktraceForMultipleThreads'",
76
+ + backtrace[0]
77
+ + assert_equal __FILE__ + ":45:in `loop'",
78
+ + backtrace[1]
79
+ + assert_equal __FILE__ + ":45:in `testCollectMeaningfulBacktraceForMultipleThreads'",
80
+ + backtrace[2]
81
+ + assert_equal __FILE__ + ":44:in `initialize'",backtrace[3]
82
+ + assert_equal __FILE__ + ":44:in `new'", backtrace[4]
83
+ + assert_equal __FILE__ + ":44:in `testCollectMeaningfulBacktraceForMultipleThreads'",
84
+ + backtrace[5]
85
+ +
86
+ + backtrace = backtraces[second_thread]
87
+ + assert_not_nil backtrace
88
+ + assert_equal __FILE__ + ":53:in `testCollectMeaningfulBacktraceForMultipleThreads'",
89
+ + backtrace[0]
90
+ + assert_equal __FILE__ + ":52:in `loop'", backtrace[1]
91
+ + assert_equal __FILE__ + ":52:in `testCollectMeaningfulBacktraceForMultipleThreads'",
92
+ + backtrace[2]
93
+ + assert_equal __FILE__ + ":51:in `initialize'",backtrace[3]
94
+ + assert_equal __FILE__ + ":51:in `new'", backtrace[4]
95
+ + assert_equal __FILE__ + ":51:in `testCollectMeaningfulBacktraceForMultipleThreads'",
96
+ + backtrace[5]
97
+ + end
98
+ +
99
+ +end
100
+ +
101
+ Index: eval.c
102
+ ===================================================================
103
+ --- eval.c (revision 16289)
104
+ +++ eval.c (working copy)
105
+ @@ -7969,6 +7969,17 @@
106
+ ruby_safe_level = safe;
107
+ }
108
+
109
+ +/* Hash (Thread => Backtrace) used to collect backtrace for each threads. */
110
+ +static VALUE backtrace_for_each_thread;
111
+ +
112
+ +static int backtrace_level_for_each_thread;
113
+ +
114
+ +static VALUE
115
+ +switch_thread_context_to_collect_backtrace(rb_thread_t next);
116
+ +
117
+ +static VALUE
118
+ +rb_f_caller_for_all_threads();
119
+ +
120
+ void
121
+ Init_eval()
122
+ {
123
+ @@ -8014,6 +8025,7 @@
124
+ rb_define_global_function("fail", rb_f_raise, -1);
125
+
126
+ rb_define_global_function("caller", rb_f_caller, -1);
127
+ + rb_define_global_function("caller_for_all_threads", rb_f_caller_for_all_threads, -1);
128
+
129
+ rb_define_global_function("exit", rb_f_exit, -1);
130
+ rb_define_global_function("abort", rb_f_abort, -1);
131
+ @@ -10206,6 +10218,7 @@
132
+ #define RESTORE_RAISE 5
133
+ #define RESTORE_SIGNAL 6
134
+ #define RESTORE_EXIT 7
135
+ +#define RESTORE_BACKTRACE 8
136
+
137
+ extern VALUE *rb_gc_stack_start;
138
+ #ifdef __ia64
139
+ @@ -10313,6 +10326,15 @@
140
+ }
141
+ rb_exc_raise(th_raise_exception);
142
+ break;
143
+ + case RESTORE_BACKTRACE:
144
+ + rb_hash_aset(backtrace_for_each_thread, curr_thread->thread,
145
+ + backtrace(backtrace_level_for_each_thread));
146
+ + if (curr_thread != main_thread) {
147
+ + switch_thread_context_to_collect_backtrace(curr_thread->next);
148
+ + } else {
149
+ + /* Circled back to main thread, cycle is complete. */
150
+ + }
151
+ + break;
152
+ case RESTORE_NORMAL:
153
+ default:
154
+ break;
155
+ @@ -13240,3 +13262,74 @@
156
+ argv[1] = val;
157
+ rb_f_throw(2, argv);
158
+ }
159
+ +
160
+ +static VALUE
161
+ +switch_thread_context_to_collect_backtrace(rb_thread_t next)
162
+ +{
163
+ + if (THREAD_SAVE_CONTEXT(curr_thread)) {
164
+ + return Qnil;
165
+ + }
166
+ + curr_thread = next;
167
+ + rb_thread_restore_context(next, RESTORE_BACKTRACE);
168
+ + return Qnil;
169
+ +}
170
+ +
171
+ +
172
+ +/*
173
+ + * call-seq:
174
+ + * caller_for_all_threads(start=1) => array
175
+ + *
176
+ + * Returns the current execution stack for all threads
177
+ + * ---a hash whose keys are thread instances and values
178
+ + * the thread caller backtrace.
179
+ + *
180
+ + * Backtraces are array of hashes indicating location on the
181
+ + * stack. Hash keys include ``<em>:line</em>'' or ``<em>:file</em>''
182
+ + * and ``<em>:method'</em>''.
183
+ + *
184
+ + * The optional _start_ parameter
185
+ + * determines the number of initial stack entries to omit from the
186
+ + * result.
187
+ + *
188
+ + * def a(skip)
189
+ + * caller_for_all_threads(skip)
190
+ + * end
191
+ + * def b(skip)
192
+ + * a(skip)
193
+ + * end
194
+ + * def c(skip)
195
+ + * b(skip)
196
+ + * end
197
+ + * c(0) #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10"]
198
+ + * c(1) #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11"]
199
+ + * c(2) #=> ["prog:8:in `c'", "prog:12"]
200
+ + * c(3) #=> ["prog:13"]
201
+ + */
202
+ +static VALUE
203
+ +rb_f_caller_for_all_threads(argc, argv)
204
+ + int argc;
205
+ + VALUE *argv;
206
+ +{
207
+ + volatile int critical;
208
+ + VALUE level;
209
+ + VALUE result;
210
+ +
211
+ + rb_scan_args(argc, argv, "01", &level);
212
+ + backtrace_level_for_each_thread = NIL_P(level) ? 0 : NUM2INT(level);
213
+ + if (backtrace_level_for_each_thread < 0) {
214
+ + rb_raise(rb_eArgError, "negative level (%d)", backtrace_level_for_each_thread);
215
+ + }
216
+ +
217
+ + critical = rb_thread_critical;
218
+ + rb_thread_critical = Qtrue;
219
+ +
220
+ + backtrace_for_each_thread = rb_hash_new();
221
+ + switch_thread_context_to_collect_backtrace(main_thread->next);
222
+ +
223
+ + result = backtrace_for_each_thread;
224
+ + backtrace_for_each_thread = Qnil;
225
+ + backtrace_for_each_thread = 0;
226
+ +
227
+ + rb_thread_critical = critical;
228
+ + return result;
229
+ +}