xray 1.1

Sign up to get free protection for your applications and to get access to all the features.
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
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,12 @@
1
+ #!/usr/sbin/dtrace -s
2
+ #pragma D option quiet
3
+ #pragma D option aggsortrev
4
+ #pragma D option dynvarsize=64m
5
+ #pragma D option ustackframes=64
6
+ #pragma D option strsize=2048
7
+
8
+ profile-997
9
+ /pid == $target/
10
+ {
11
+ @num[ustack()] = count();
12
+ }
@@ -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
+
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/xray/thread_dump_signal_handler'
2
+ require File.dirname(__FILE__) + '/xray/dtrace/tracer'
@@ -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