XRay 1.0.1 → 1.0.3

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 CHANGED
@@ -1,19 +1,44 @@
1
+ = XRay
2
+
3
+ * http://rubyforge.org/projects/xray
4
+
5
+ == Description
6
+
1
7
  XRay provides a lightweight yet powerful toolbox for troubleshooting Ruby
2
- applications when things stop making sense. Includes GDB and DTrace tooling.
8
+ applications when things stop making sense. XRay includes GDB and DTrace
9
+ tooling as well as a Thread Dump utility that can dump the stack trace
10
+ of all the thread in your Ruby VM when you send a +QUIT+ signal.
11
+
12
+ == GDB
13
+
14
+ Copy the +gdb_macros+ file provided in the gem as your ~/.gdbinit file.
15
+ You will find more details on how to use them, in my
16
+ {Troubleshooting Ruby Shortcut}[http://ph7spot.com/publications/troubleshooting_ruby_processes]
3
17
 
4
- *** GDB ***
18
+ == Thread Dump
5
19
 
20
+ After patching your Ruby VM with {caller_for_all_threads_patch_for_MRI_1.8.6.diff}[http://xray.rubyforge.org/svn/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.6.diff]
21
+ as explained in {this document}[http://ph7spot.com/caller_for_all_threads], you can install a signal
22
+ handler in charge of dumping the stack trace for all the threads
23
+ in your Ruby VM with:
6
24
 
7
- Copy +gdb_macros+ file provided in the gem as your ~/.gdbinit file.
25
+ require "xray"
26
+ require "xray/thread_dump_signal_handler"
8
27
 
9
- *** Fire DTrace Application Probes ***
28
+ You can then trigger a thread dump at any time with
10
29
 
11
- See XRay::DTrace::Tracer
30
+ kill -QUIT <pid of your ruby process>
12
31
 
13
- *** Out-of-the-box Rails DTrace Instrumentation ***
32
+ == DTrace
14
33
 
15
- You are one require away from triggering automatically DTrace events for
16
- Rails requests, database access and template rendering. As simple as
34
+ === Fire DTrace Application Probes
35
+
36
+ See XRay::DTrace::Tracer
37
+
38
+ === Out-of-the-box Rails DTrace Instrumentation ***
39
+
40
+ You are one require away from triggering automatically DTrace events for
41
+ Rails requests, database access and template rendering. As simple as
17
42
 
18
43
  # environment.rb
19
44
  Rails::Initializer.run do |config|
@@ -25,10 +50,14 @@ applications when things stop making sense. Includes GDB and DTrace tooling.
25
50
  end
26
51
  end
27
52
 
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
53
+ See
54
+ * lib/xray/dtrace/railsenable_tracing.rb
55
+ * lib/xray/dtrace/action_controller_tracing_extension.rb
56
+ * lib/xray/dtrace/active_record_tracing_extension.rb
57
+
32
58
 
59
+ == Author
33
60
 
61
+ Philippe Hanrigou,
62
+ http://ph7spot.com
34
63
 
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/clean'
6
+ require 'rubygems'
7
+
8
+ CLEAN.include '**/*.o'
9
+ CLEAN.include '**/*.so'
10
+ CLEAN.include '**/*.bundle'
11
+ CLOBBER.include '**/*.log'
12
+ CLOBBER.include '**/Makefile'
13
+ CLOBBER.include '**/extconf.h'
14
+
15
+ desc 'Default: run unit tests.'
16
+ task :default => :test
17
+
18
+
19
+ desc 'Test XRay.'
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.libs << 'lib'
22
+ t.pattern = 'test/**/*_test.rb'
23
+ t.verbose = true
24
+ end
25
+
26
+ desc 'Generate documentation for XRay.'
27
+ Rake::RDocTask.new(:rdoc) do |rdoc|
28
+ rdoc.rdoc_dir = 'rdoc'
29
+ rdoc.title = 'XRay'
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ rdoc.rdoc_files.include('README')
32
+ rdoc.rdoc_files.include('lib/**/*.rb')
33
+ end
34
+
35
+ specification = Gem::Specification.new do |s|
36
+ s.name = "XRay"
37
+ s.summary = "Dump backtrace for all threads."
38
+ s.version = "1.0.3"
39
+ s.author = "Philippe Hanrigou"
40
+ s.email = 'xray-developer@rubyforge.org'
41
+ s.homepage = "http://xray.rubyforge.com"
42
+ s.rubyforge_project = 'xray'
43
+ s.platform = Gem::Platform::RUBY
44
+ s.files = FileList['lib/**/*.rb'] + FileList['test/**/*.rb'] +
45
+ FileList['**/*.d'] + FileList['**/*.diff'] +
46
+ [ 'gdb_macros', 'Rakefile' ]
47
+ s.require_path = "lib"
48
+ s.extensions = []
49
+ s.rdoc_options << '--title' << 'XRay' << '--main' << 'README' << '--line-numbers'
50
+ s.has_rdoc = true
51
+ s.extra_rdoc_files = ['README']
52
+ s.test_file = "test/all_tests.rb"
53
+ end
54
+
55
+ Rake::GemPackageTask.new(specification) do |package|
56
+ package.need_zip = false
57
+ package.need_tar = false
58
+ end
59
+
60
+ desc "Publish RDoc on Rubyforge website"
61
+ task :publish_rdoc => :rdoc do
62
+ sh "scp -i ~/.ssh/id_dsa -r rdoc/* #{ENV['USER']}@rubyforge.org:/var/www/gforge-projects/xray"
63
+ 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
+ +}
@@ -131,7 +131,7 @@ Index: tracer.c
131
131
  ===================================================================
132
132
  --- tracer.c (revision 0)
133
133
  +++ tracer.c (revision 0)
134
- @@ -0,0 +1,62 @@
134
+ @@ -0,0 +1,73 @@
135
135
  +#include "ruby.h"
136
136
  +
137
137
  +#ifdef ENABLE_DTRACE
@@ -188,11 +188,22 @@ Index: tracer.c
188
188
  + return ret;
189
189
  +}
190
190
  +
191
+ +static VALUE
192
+ +ruby_dtrace_enabled(klass)
193
+ + VALUE klass;
194
+ +{
195
+ +#ifdef ENABLE_DTRACE
196
+ + return RUBY_RUBY_PROBE_ENABLED() ? Qtrue : Qfalse;
197
+ +#else
198
+ + return Qfalse;
199
+ +#endif
200
+ +}
191
201
  +
192
202
  +void Init_Tracer()
193
203
  +{
194
204
  + rb_mDtrace = rb_define_module("Tracer");
195
205
  + rb_define_module_function(rb_mDtrace, "fire", ruby_dtrace_fire, -1);
206
+ + rb_define_module_function(rb_mDtrace, "enabled?", ruby_dtrace_enabled, 0);
196
207
  +}
197
208
  Index: dtrace.d
198
209
  ===================================================================
@@ -225,7 +236,7 @@ Index: dtrace.d
225
236
  +#pragma D attributes Private/Private/Common provider ruby function
226
237
  +#pragma D attributes Evolving/Evolving/Common provider ruby name
227
238
  +#pragma D attributes Evolving/Evolving/Common provider ruby args
228
-
239
+ +
229
240
  Index: common.mk
230
241
  ===================================================================
231
242
  --- common.mk (revision 16089)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: XRay
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philippe Hanrigou
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-20 00:00:00 -07:00
12
+ date: 2008-05-29 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -27,8 +27,8 @@ files:
27
27
  - lib/xray/dtrace/rails/enable_tracing.rb
28
28
  - lib/xray/dtrace/tracer.rb
29
29
  - lib/xray/enable_thread_aware_dispatcher.rb
30
- - lib/xray/rails_stack_signal_handler.rb
31
30
  - lib/xray/thread_aware_dispatcher.rb
31
+ - lib/xray/thread_dump_signal_handler.rb
32
32
  - lib/xray/xray.rb
33
33
  - test/all_tests.rb
34
34
  - test/functional/dtrace/tracer_test.rb
@@ -37,9 +37,11 @@ files:
37
37
  - test/unit/thread_aware_dispatcher_test.rb
38
38
  - test/unit/xray/dtrace/tracer_test.rb
39
39
  - application-probes.d
40
- - patch-for-dtrace-instrumentation-of-matz-1.8.6-p114.diff
41
- - patch-for-joyent-mri-1.8.6-on-leopard.diff
40
+ - patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.6.diff
41
+ - patches_for_mri/patch-for-dtrace-instrumentation-of-matz-1.8.6-p114.diff
42
+ - patches_for_mri/patch-for-joyent-mri-1.8.6-on-mac-os-x-leopard.diff
42
43
  - gdb_macros
44
+ - Rakefile
43
45
  - README
44
46
  has_rdoc: true
45
47
  homepage: http://xray.rubyforge.com
@@ -1,15 +0,0 @@
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