xray 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +65 -0
- data/bin/install_dtrace_on_ubuntu +30 -0
- data/bin/xray_profile_ruby_function_calls.d +54 -0
- data/bin/xray_top_10_busiest_code_path_for_process.d +12 -0
- data/bin/xray_top_10_busiest_code_path_on_system.d +35 -0
- data/bin/xray_trace_all_custom_ruby_probes.d +25 -0
- data/bin/xray_trace_all_ruby_probes.d +20 -0
- data/bin/xray_trace_memcached.d +20 -0
- data/bin/xray_trace_mysql.d +30 -0
- data/bin/xray_trace_rails_response_times.d +114 -0
- data/lib/xray.rb +2 -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 +86 -0
- data/lib/xray/dtrace/tracer/joyent.rb +25 -0
- data/lib/xray/dtrace/tracer/leopard.rb +28 -0
- data/lib/xray/dtrace/usdt/provider_extensions.rb +45 -0
- data/lib/xray/thread_dump_signal_handler.rb +31 -0
- data/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.6.diff +229 -0
- data/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.7.diff +229 -0
- data/patches_for_mri/patch-for-dtrace-instrumentation-of-matz-1.8.6-p114.diff +522 -0
- data/patches_for_mri/patch-for-joyent-mri-1.8.6-on-mac-os-x-leopard.diff +20645 -0
- data/rails/init.rb +2 -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 +9 -0
- data/test/unit/xray/dtrace/tracer_test.rb +54 -0
- metadata +84 -0
data/README
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= XRay
|
2
|
+
|
3
|
+
* http://rubyforge.org/projects/xray
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
XRay provides a lightweight yet powerful toolbox for troubleshooting Ruby
|
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]
|
17
|
+
|
18
|
+
== Thread Dump
|
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
|
+
(or {caller_for_all_threads_patch_for_MRI_1.8.7.diff}[http://xray.rubyforge.org/svn/patches_for_mri/caller_for_all_threads_patch_for_MRI_1.8.7.diff])
|
22
|
+
as explained in {this document}[http://ph7spot.com/caller_for_all_threads], you can install a signal
|
23
|
+
handler in charge of dumping the stack trace for all the threads
|
24
|
+
in your Ruby VM with:
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "xray/thread_dump_signal_handler"
|
28
|
+
|
29
|
+
You can then trigger a thread dump at any time with
|
30
|
+
|
31
|
+
kill -QUIT <pid of your ruby process>
|
32
|
+
|
33
|
+
== DTrace
|
34
|
+
|
35
|
+
=== Fire DTrace Application Probes
|
36
|
+
|
37
|
+
See XRay::DTrace::Tracer
|
38
|
+
|
39
|
+
=== Out-of-the-box Rails DTrace Instrumentation ***
|
40
|
+
|
41
|
+
You are one require away from triggering automatically DTrace events for
|
42
|
+
Rails requests, database access and template rendering. As simple as
|
43
|
+
|
44
|
+
# environment.rb
|
45
|
+
Rails::Initializer.run do |config|
|
46
|
+
|
47
|
+
...
|
48
|
+
|
49
|
+
config.after_initialize do
|
50
|
+
require "rubygems"
|
51
|
+
require "xray/dtrace/rails/enable_tracing"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
See
|
56
|
+
* lib/xray/dtrace/railsenable_tracing.rb
|
57
|
+
* lib/xray/dtrace/action_controller_tracing_extension.rb
|
58
|
+
* lib/xray/dtrace/active_record_tracing_extension.rb
|
59
|
+
|
60
|
+
|
61
|
+
== Author
|
62
|
+
|
63
|
+
Philippe Hanrigou,
|
64
|
+
http://ph7spot.com
|
65
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
#
|
3
|
+
# Painless DTrace install for Ubuntu
|
4
|
+
#
|
5
|
+
|
6
|
+
DISTRIBUTION=dtrace-20081028
|
7
|
+
|
8
|
+
download()
|
9
|
+
{
|
10
|
+
sudo apt-get install curl
|
11
|
+
if [ ! -e ${DISTRIBUTION} ]; then
|
12
|
+
echo "\n==== Downloading DTrace for Linux ====\n"
|
13
|
+
curl -O ftp://crisp.dynalias.com/pub/release/website/dtrace/${DISTRIBUTION}.tar.bz2
|
14
|
+
tar jxvf ${DISTRIBUTION}.tar.bz2
|
15
|
+
fi
|
16
|
+
}
|
17
|
+
|
18
|
+
install_dependencies()
|
19
|
+
{
|
20
|
+
sudo apt-get install zlib1g-dev flex bison elfutils libelf-dev libc6-dev linux-libc-dev
|
21
|
+
}
|
22
|
+
|
23
|
+
build()
|
24
|
+
{
|
25
|
+
make -C ${DISTRIBUTION} clean all
|
26
|
+
}
|
27
|
+
|
28
|
+
download
|
29
|
+
install_dependencies
|
30
|
+
build
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option aggsortrev
|
4
|
+
#pragma D option dynvarsize=64m
|
5
|
+
|
6
|
+
/*
|
7
|
+
* Trace all ruby function calls and display their total time, average time
|
8
|
+
* an invocation number (slower first).
|
9
|
+
*
|
10
|
+
* Usage:
|
11
|
+
*
|
12
|
+
* sudo /usr/bin/xray_profile_ruby_function_calls.d -p <a pid>
|
13
|
+
*
|
14
|
+
* sudo /usr/bin/xray_profile_ruby_function_calls.d -c "ruby -v"
|
15
|
+
*
|
16
|
+
* sudo dtrace -s /usr/bin/xray_profile_ruby_function_calls.d -p <a pid>
|
17
|
+
*/
|
18
|
+
|
19
|
+
this string str;
|
20
|
+
|
21
|
+
dtrace:::BEGIN
|
22
|
+
{
|
23
|
+
printf("Tracing, please be patient... Ctrl-C to interrupt.\n");
|
24
|
+
depth = 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
ruby$target:::function-entry
|
28
|
+
{
|
29
|
+
self->depth++;
|
30
|
+
self->start[copyinstr(arg0), copyinstr(arg1), self->depth] = timestamp;
|
31
|
+
}
|
32
|
+
|
33
|
+
ruby$target:::function-return
|
34
|
+
/(this->class = copyinstr(arg0)) != NULL && \
|
35
|
+
(this->func = copyinstr(arg1)) != NULL && \
|
36
|
+
self->start[this->class, this->func, self->depth]/
|
37
|
+
{
|
38
|
+
this->elapsed = timestamp - self->start[this->class, this->func, self->depth];
|
39
|
+
@num[this->class, this->func] = count();
|
40
|
+
@eavg[this->class, this->func] = avg(this->elapsed);
|
41
|
+
@esum[this->class, this->func] = sum(this->elapsed);
|
42
|
+
self->start[this->class, this->func, self->depth] = 0;
|
43
|
+
self->depth--;
|
44
|
+
}
|
45
|
+
|
46
|
+
dtrace:::END
|
47
|
+
{
|
48
|
+
normalize(@eavg, 1000);
|
49
|
+
normalize(@esum, 1000);
|
50
|
+
setopt("aggsortpos", "0"); /* Sort on total time */
|
51
|
+
printf("%95s\n", "_______ ELAPSED ______");
|
52
|
+
printf("%-40s %-30s %12s %10s %6s\n", "Class", "Method", "Total (us)", "Avg (us)", "Count\n");
|
53
|
+
printa("%-40.40s %-30.30s %@12d %@10d %@6d\n", @esum, @eavg, @num);
|
54
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option dynvarsize=64m
|
4
|
+
|
5
|
+
dtrace:::BEGIN
|
6
|
+
{
|
7
|
+
printf("Tracing... Hit Ctrl-C to end.\n");
|
8
|
+
depth = 0;
|
9
|
+
}
|
10
|
+
|
11
|
+
|
12
|
+
profile-997
|
13
|
+
{
|
14
|
+
@kernel[stack(20)]=count();
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
profile-997
|
20
|
+
/arg1/ /* user level PC. Make sure that we are in user space */
|
21
|
+
{
|
22
|
+
printf("%d\n", arg1);
|
23
|
+
@userspace[execname, ustack(10)]=count();
|
24
|
+
}
|
25
|
+
|
26
|
+
END {
|
27
|
+
trunc(@kernel, 10);
|
28
|
+
trunc(@userspace, 10);
|
29
|
+
|
30
|
+
printf("%s", "\n =========== Top 10 Busiest Kernel Path =============\n");
|
31
|
+
printa(@kernel);
|
32
|
+
|
33
|
+
printf("%s", "\n =========== Top 10 Busiest User Path =============\n");
|
34
|
+
printa(@userspace);
|
35
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
|
4
|
+
/*
|
5
|
+
* Trace all the custom ruby probes defined in your application
|
6
|
+
* using XRay::DTrace, Apple's DTracer or Joyent's Tracer module.
|
7
|
+
*
|
8
|
+
* Usage:
|
9
|
+
*
|
10
|
+
* sudo /usr/bin/xray_trace_all_custom_ruby_probes.d -p <a pid>
|
11
|
+
*
|
12
|
+
* sudo /usr/bin/xray_trace_all_custom_ruby_probes.d -c "ruby -v"
|
13
|
+
*
|
14
|
+
* sudo dtrace -s /usr/bin/xray_trace_all_custom_ruby_probes.d -p <a pid>
|
15
|
+
*/
|
16
|
+
|
17
|
+
dtrace:::BEGIN
|
18
|
+
{
|
19
|
+
printf("Tracing... Hit Ctrl-C to end.\n");
|
20
|
+
}
|
21
|
+
|
22
|
+
ruby$target:::ruby-probe
|
23
|
+
{
|
24
|
+
printf("=> %d == cpu: %2d == %15s '%s'\n", timestamp, cpu, copyinstr(arg0), copyinstr(arg1))
|
25
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option dynvarsize=64m
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Trace all the probes of the ruby provider
|
7
|
+
*
|
8
|
+
* Usage:
|
9
|
+
*
|
10
|
+
* sudo /usr/bin/xray_trace_all_ruby_probes.d -p <a pid>
|
11
|
+
*
|
12
|
+
* sudo /usr/bin/xray_trace_all_ruby_probes.d -c "ruby -v"
|
13
|
+
*
|
14
|
+
* sudo dtrace -s /usr/bin/xray_trace_all_ruby_probes.d -p <a pid>
|
15
|
+
*/
|
16
|
+
|
17
|
+
ruby$target:::
|
18
|
+
{
|
19
|
+
printf("=> cpu %2d == %-15.15s == %-15.15s == %s\n", cpu, probename, copyinstr(arg0), copyinstr(arg1))
|
20
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option dynvarsize=64m
|
4
|
+
|
5
|
+
|
6
|
+
pid*:memcached:*do_item_update*:entry {
|
7
|
+
printf("COMMAND == %s \n", probefunc);
|
8
|
+
}
|
9
|
+
|
10
|
+
pid*:memcached:process_get_command:entry {
|
11
|
+
self->conn_id = arg0;
|
12
|
+
/* self->pointer_to_string = copyin(arg1, 4); */
|
13
|
+
printf("GET == connection %d == update %p %d\n", arg0, arg1, arg2);
|
14
|
+
}
|
15
|
+
|
16
|
+
pid*:memcached:process_update_command:entry {
|
17
|
+
self->conn_id = arg0;
|
18
|
+
/* self->pointer_to_string = copyin(arg1, 4); */
|
19
|
+
printf("UPDATE == connection %d == update %p %d\n", arg0, arg1, arg2);
|
20
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option dynvarsize=64m
|
4
|
+
|
5
|
+
|
6
|
+
/*
|
7
|
+
* Inspired by Joyent's http://www.joyeur.com/2007/10/03/using-dtrace-on-mysql
|
8
|
+
*/
|
9
|
+
|
10
|
+
/*
|
11
|
+
* MySQL query parsing (captures all incoming queries)
|
12
|
+
*/
|
13
|
+
|
14
|
+
/* TODO For example we can see what queries are executed the most: => Top 10 queries */
|
15
|
+
pid*::*mysql*:entry
|
16
|
+
{
|
17
|
+
printf("%Y %s\n", walltimestamp, copyinstr(arg1));
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
/*
|
22
|
+
* Now lets say we want to find out how long a query took to execute.
|
23
|
+
* The function mysql_execute_command does the actual execution of
|
24
|
+
* the queries so all we do here is subtract the entry and return
|
25
|
+
* timestamps of this function.
|
26
|
+
*/
|
27
|
+
pid*:mysqld:*mysql_execute_command*:entry
|
28
|
+
{
|
29
|
+
printf("OK");
|
30
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/sbin/dtrace -s
|
2
|
+
#pragma D option quiet
|
3
|
+
#pragma D option aggsortrev
|
4
|
+
#pragma D option dynvarsize=64m
|
5
|
+
|
6
|
+
/*
|
7
|
+
* Trace Rails controller actions and database response times.
|
8
|
+
*
|
9
|
+
* Report average response time, total elapsed time and invocation count
|
10
|
+
* for all controller actions and datase queries.
|
11
|
+
*
|
12
|
+
* Also print response time distribution diagrams for global and individual
|
13
|
+
* action / query.
|
14
|
+
*
|
15
|
+
* Usage:
|
16
|
+
*
|
17
|
+
* sudo /usr/bin/xray_trace_rails_response_times.d -p <a pid>
|
18
|
+
*
|
19
|
+
* sudo /usr/bin/xray_trace_rails_response_times.d -c "ruby -v"
|
20
|
+
*
|
21
|
+
* sudo dtrace -s /usr/bin/xray_trace_rails_response_times.d -p <a pid>
|
22
|
+
*/
|
23
|
+
this string name;
|
24
|
+
this string action;
|
25
|
+
this string query;
|
26
|
+
|
27
|
+
dtrace:::BEGIN
|
28
|
+
{
|
29
|
+
printf("Tracing... Hit Ctrl-C to end.\n");
|
30
|
+
depth = 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
ruby$target::ruby_dtrace_probe:
|
34
|
+
/(this->name = copyinstr(arg0)) == "request-start"/
|
35
|
+
{
|
36
|
+
self->request_start[copyinstr(arg1)] = timestamp;
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
ruby$target::ruby_dtrace_probe:
|
41
|
+
/(this->name = copyinstr(arg0)) == "request-end" && self->request_start[(this->action = copyinstr(arg1))]/
|
42
|
+
{
|
43
|
+
this->elapsed = timestamp - self->request_start[this->action];
|
44
|
+
@re_count[this->action] = count();
|
45
|
+
@re_eavg[this->action] = avg(this->elapsed);
|
46
|
+
@re_esum[this->action] = sum(this->elapsed);
|
47
|
+
@re_edist[this->action] = quantize(this->elapsed);
|
48
|
+
@re_all_edist["All Requests"] = quantize(this->elapsed);
|
49
|
+
self->request_start[this->action] = 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
ruby$target::ruby_dtrace_probe:
|
53
|
+
/(this->name = copyinstr(arg0)) == "db-start"/
|
54
|
+
{
|
55
|
+
self->db_start[copyinstr(arg1)] = timestamp;
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
ruby$target::ruby_dtrace_probe:
|
60
|
+
/(this->name = copyinstr(arg0)) == "db-end" && self->db_start[(this->query = copyinstr(arg1))]/
|
61
|
+
{
|
62
|
+
@db_count[this->query] = count();
|
63
|
+
this->elapsed = timestamp - self->db_start[this->query];
|
64
|
+
@db_eavg[this->query] = avg(this->elapsed);
|
65
|
+
@db_esum[this->query] = sum(this->elapsed);
|
66
|
+
@db_edist[this->query] = quantize(this->elapsed);
|
67
|
+
@db_all_edist["All Queries"] = quantize(this->elapsed);
|
68
|
+
self->db_start[this->query] = 0;
|
69
|
+
}
|
70
|
+
|
71
|
+
dtrace:::END
|
72
|
+
{
|
73
|
+
normalize(@re_eavg, 1000);
|
74
|
+
normalize(@re_esum, 1000);
|
75
|
+
normalize(@db_eavg, 1000);
|
76
|
+
normalize(@db_esum, 1000);
|
77
|
+
|
78
|
+
printf("\n================================================\n");
|
79
|
+
printf(" Controller Response Times\n");
|
80
|
+
printf("================================================\n\n");
|
81
|
+
|
82
|
+
setopt("aggsortpos", "0"); /* Sort on avg time */
|
83
|
+
printf("%-50s %s\n", " _________ Action _________", "_ Avg. (us) _ Total (us) _ Count _\n");
|
84
|
+
printa("%-50.50s %@12d %@12d %@6d\n", @re_eavg, @re_esum, @re_count);
|
85
|
+
|
86
|
+
|
87
|
+
printf("\n================================================\n");
|
88
|
+
printf(" Database Response Times\n");
|
89
|
+
printf("================================================\n");
|
90
|
+
|
91
|
+
setopt("aggsortpos", "0"); /* Sort on avg time */
|
92
|
+
printf("%-100s %s\n", " _________ Query _________", "_ Avg. (us) _ Total (us) _ Count _\n");
|
93
|
+
printa("%-100.100s %@12d %@12d %@6d\n", @db_eavg, @db_esum, @db_count);
|
94
|
+
|
95
|
+
printf("\n================================================\n");
|
96
|
+
printf(" Response Time Summary\n");
|
97
|
+
printf("================================================\n\n");
|
98
|
+
|
99
|
+
printa(@re_all_edist);
|
100
|
+
printa(@db_all_edist);
|
101
|
+
|
102
|
+
printf("\n================================================\n");
|
103
|
+
printf(" Controller Response Time Distribution\n");
|
104
|
+
printf("================================================\n\n");
|
105
|
+
|
106
|
+
printa(@re_edist);
|
107
|
+
|
108
|
+
printf("\n================================================\n");
|
109
|
+
printf(" DB Response Time Distribution\n");
|
110
|
+
printf("================================================\n\n");
|
111
|
+
|
112
|
+
printa(@db_edist);
|
113
|
+
}
|
114
|
+
|
data/lib/xray.rb
ADDED
@@ -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, extra_options = {}, &block)
|
14
|
+
firing('render', options.to_s) do
|
15
|
+
render_without_tracing options, extra_options
|
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
|