ruby-dtrace 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,6 @@
1
+ == 0.0.4 / 2008-01-14
2
+ * Fix the very basic Rails plugin.
3
+
1
4
  == 0.0.3 / 2008-01-06
2
5
 
3
6
  * Add a DtraceData class which consumers return, containing
@@ -32,10 +32,11 @@ plugin/dtrace/bin/dtracer.rb
32
32
  plugin/dtrace/init.rb
33
33
  plugin/dtrace/lib/dtrace_helper.rb
34
34
  plugin/dtrace/lib/dtrace_report.rb
35
- plugin/dtrace/lib/dtrace_tracer.rb
36
35
  plugin/dtrace/lib/dtracer.rb
37
36
  plugin/dtrace/lib/dtracer_client.rb
38
37
  plugin/dtrace/public/stylesheets/dtrace.css
38
+ plugin/dtrace/scripts/default.d
39
+ plugin/dtrace/scripts/rails_mysql.d
39
40
  plugin/dtrace/tasks/dtrace.rake
40
41
  plugin/dtrace/test/dtrace_test.rb
41
42
  plugin/dtrace/views/dtrace/_report.rhtml
@@ -63,6 +63,12 @@ require 'dtracedata'
63
63
  # end
64
64
 
65
65
  class Dtrace
66
- VERSION = '0.0.3'
66
+ VERSION = '0.0.4'
67
+
68
+ STATUS_NONE = 0
69
+ STATUS_OKAY = 1
70
+ STATUS_EXITED = 2
71
+ STATUS_FILLED = 3
72
+ STATUS_STOPPED = 4
67
73
  end
68
74
 
@@ -1,4 +1,81 @@
1
1
  Dtrace
2
2
  ======
3
3
 
4
- Description goes here
4
+ A simple plugin to run a DTrace script while your Rails app runs.
5
+
6
+ Installation:
7
+
8
+ script/plugin install http://ruby-dtrace.rubyforge.org/svn/trunk/plugin/dtrace
9
+
10
+ Or copy the plugin/dtrace directory from the installed ruby-dtrace gem
11
+ to your app's vendor/plugins directory.
12
+
13
+ Choose a mode of operation:
14
+
15
+ The process running DTrace needs additional privileges: on Solaris,
16
+ you can grant the dtrace_* privileges, and on both Leopard and Solaris
17
+ you can run the process as root.
18
+
19
+ This plugin can run in two modes: you can run the whole app with
20
+ DTrace privileges, or you can run a helper process with the
21
+ privileges, and the app will use DRb to communicate with it.
22
+
23
+ If you choose to run the app as root, you need to set the :tracer
24
+ option to the dtrace macro to :self, and if you choose to run the
25
+ helper as root, you need to set the :tracer option to :helper.
26
+
27
+ Configuring the plugin:
28
+
29
+ Add the following line to either the specific controller you want
30
+ traced, or to the ApplicationController, in which case it will be
31
+ applied to every controller in your app:
32
+
33
+ dtrace :on, :tracer => :self
34
+
35
+ or
36
+
37
+ dtrace :on, :tracer => :helper
38
+
39
+ You can also set the name of the script to be run, from the
40
+ vendor/plugins/dtrace/scripts/ directory with the :script option:
41
+
42
+ dtrace :on, :tracer => :self, :script => 'rails_mysql.d'
43
+
44
+ There are two scripts in the distribution:
45
+ * default.d
46
+ - a very simple script to log system calls, which doesn't require
47
+ a DTrace-enabled ruby.
48
+
49
+ * rails_mysql.d
50
+ - a simplified version of the script shown here:
51
+ http://blogs.sun.com/bmc/entry/dtrace_on_rails, which relies on
52
+ DTrace-enabled ruby, and the use of MySQL with the
53
+ libmysqlclient-based adapter, not the pure-ruby adapter.
54
+
55
+ Troubleshooting:
56
+
57
+ If you don't get a DTrace report, check the log for messages like:
58
+
59
+ DTrace start setup: unable to open dtrace (not root?)
60
+
61
+ (indicating a lack of privileges to start DTrace: check your setting
62
+ of the :tracer option, and how you are starting the application and/or
63
+ helper).
64
+
65
+ DTrace start compile: probe description pid14484::mysql_real_query:entry does not match any probes
66
+
67
+ (indicating the function mysql_real_query isn't found: either you're
68
+ not using MySQL, or you are using the pure ruby client).
69
+
70
+ DTrace start compile: probe description ruby15253:::function-entry does not match any probes
71
+
72
+ (indicating you are not using a DTrace-enabled ruby binary)
73
+
74
+ A DRb::DRbConnError reported by Rails, like this:
75
+
76
+ druby://localhost:2999 - #<Errno::ECONNREFUSED: Connection refused - connect(2)>
77
+
78
+ indicates you've selected the :helper option, but the helper isn't
79
+ running. Check that your app can connect to localhost:2999 for the DRb
80
+ service the helper provides.
81
+
@@ -20,5 +20,10 @@ require 'dtracer'
20
20
  here = "druby://localhost:2999"
21
21
  tracer = Dtracer.new
22
22
  DRb.start_service here, tracer
23
- DRb.thread.join
23
+ puts "DTrace helper started"
24
+ begin
25
+ DRb.thread.join
26
+ rescue Interrupt
27
+ exit 0
28
+ end
24
29
 
@@ -10,34 +10,49 @@ module DtraceReport
10
10
  module DtraceMacro
11
11
  def dtrace(enable=:on, options={})
12
12
  if enable == :on
13
+
14
+ # Set tracer type, in-process or helper-process
13
15
  if options[:tracer] == :self
14
- DtraceReport.tracer = Dtracer.new
16
+ tracer = Dtracer.new
15
17
  elsif options[:tracer] == :helper
16
- DtracerReport.tracer = DtracerClient.new
18
+ tracer = DtracerClient.new
17
19
  else
18
20
  raise "tracer option is self or helper"
19
21
  end
22
+
23
+ tracer.logger = logger
24
+
25
+ # Set script, or default
26
+ if options[:script]
27
+ tracer.script = options[:script]
28
+ else
29
+ tracer.script = 'default.d'
30
+ end
31
+
32
+ DtraceReport.tracer = tracer
20
33
  end
21
34
  end
22
35
  end
23
36
 
24
37
  attr_reader :dtrace_report
38
+ attr_reader :dtrace_script
25
39
 
26
40
  protected
27
41
  def self.tracer=(tracer)
28
42
  @@tracer = tracer
29
43
  end
30
-
44
+
31
45
  def enable_dtrace
32
46
  @@tracer.start_dtrace($$)
33
47
  end
34
48
 
35
49
  def append_dtrace_report
50
+ @dtrace_script = @@tracer.script
36
51
  @dtrace_report = @@tracer.end_dtrace
37
52
  # yuck!
38
53
  old_template_root = @template.base_path
39
54
  begin
40
- @template.view_paths = File.join(RAILS_ROOT, 'vendor/plugins/dtrace/views')
55
+ @template.view_paths = File.expand_path(File.dirname(__FILE__) + '/../views')
41
56
  response.body.gsub!(/<\/body/, @template.render(:partial => 'dtrace/report') + '</body')
42
57
  ensure
43
58
  @template.view_paths = old_template_root
@@ -1,84 +1,52 @@
1
1
  require 'dtrace'
2
2
 
3
3
  class Dtracer
4
-
5
- def start_dtrace(pid)
6
- progtext = <<EOD
7
- self string uri;
8
-
9
- pid$1::mysql_real_query:entry
10
- {
11
- @queries[copyinstr(arg1)] = count();
12
- }
13
-
14
- ruby$1:::function-entry
15
- {
16
- @rbclasses[this->class = copyinstr(arg0)] = count();
17
- this->sep = strjoin(this->class, "#");
18
- @rbmethods[strjoin(this->sep, copyinstr(arg1))] = count();
19
- }
20
-
21
- syscall:::entry
22
- {
23
- @syscalls[probefunc] = count();
24
- }
25
-
26
- END
27
- {
28
- printf("report:syscalls");
29
- printa(@syscalls);
30
- printf("report:rbclasses");
31
- printa(@rbclasses);
32
- printf("report:rbmethods");
33
- printa(@rbmethods);
34
- printf("report:queries");
35
- printa(@queries);
36
- }
4
+ attr_writer :logger, :dprogram
5
+ attr_reader :script
37
6
 
38
- EOD
7
+ def script=(script)
8
+ @script = script
9
+ scriptdir = File.expand_path(File.dirname(__FILE__) + "/../scripts")
10
+ @dprogram = IO.read("#{scriptdir}/#{script}")
11
+ end
39
12
 
13
+ def start_dtrace(pid)
40
14
  begin
41
15
  @d = Dtrace.new
42
16
  @d.setopt("aggsize", "4m")
43
17
  @d.setopt("bufsize", "4m")
44
18
  rescue DtraceException => e
45
- puts "start setup: #{e.message}"
19
+ @logger.warn("DTrace start setup: #{e.message}")
46
20
  return
47
21
  end
48
22
 
49
23
  begin
50
- prog = @d.compile(progtext, pid.to_s)
24
+ prog = @d.compile(@dprogram, pid.to_s)
51
25
  prog.execute
52
26
  @d.go
53
27
  rescue DtraceException => e
54
- puts "start: #{e.message}"
28
+ @logger.warn("DTrace start compile: #{e.message}")
55
29
  end
56
-
57
30
  end
58
31
 
59
32
  def end_dtrace
60
- return {} unless @d
33
+ # Check presence of handle and correct status.
34
+ return [] unless @d && @d.status == Dtrace::STATUS_OKAY
61
35
 
62
- current_report = 'none'
36
+ dtrace_data = nil
63
37
  begin
64
- dtrace_report = Hash.new
65
38
  c = DtraceConsumer.new(@d)
66
- c.consume_once do |e|
67
- if e.respond_to? :tuple
68
- dtrace_report[current_report][e.tuple.first] = e.value
69
- elsif e.respond_to? :value
70
- if e.value =~ /report:(.*)/
71
- current_report = Regexp.last_match(1)
72
- unless dtrace_report[current_report]
73
- dtrace_report[current_report] = Hash.new
74
- end
75
- end
76
- end
39
+ c.consume_once do |d|
40
+ dtrace_data = d
77
41
  end
78
42
  rescue DtraceException => e
79
- puts "end: #{e.message}"
43
+ @logger.warn("DTrace end: #{e.message}")
44
+ end
45
+
46
+ if dtrace_data
47
+ return dtrace_data.data
48
+ else
49
+ return []
80
50
  end
81
-
82
- return dtrace_report
83
51
  end
84
52
  end
@@ -1,12 +1,20 @@
1
1
  require 'drb'
2
2
 
3
3
  class DtracerClient
4
+ attr_writer :logger
5
+ attr_reader :script
4
6
 
5
7
  def initialize
6
8
  DRb.start_service
7
9
  @tracer = DRbObject.new(nil, 'druby://localhost:2999')
8
10
  end
9
11
 
12
+ def script=(script)
13
+ @script = script
14
+ scriptdir = File.expand_path(File.dirname(__FILE__) + "/../scripts")
15
+ @tracer.dprogram = IO.read("#{scriptdir}/#{script}")
16
+ end
17
+
10
18
  def start_dtrace(pid)
11
19
  @tracer.start_dtrace(pid)
12
20
  end
@@ -14,5 +22,5 @@ class DtracerClient
14
22
  def end_dtrace
15
23
  @tracer.end_dtrace
16
24
  end
17
-
25
+
18
26
  end
@@ -0,0 +1,11 @@
1
+ syscall:::entry
2
+ /pid == $1/
3
+ {
4
+ @syscalls[probefunc] = count();
5
+ }
6
+
7
+ END
8
+ {
9
+ printf("System Calls");
10
+ printa(@syscalls);
11
+ }
@@ -0,0 +1,29 @@
1
+ pid$1::mysql_real_query:entry
2
+ {
3
+ @queries[copyinstr(arg1)] = count();
4
+ }
5
+
6
+ ruby$1:::function-entry
7
+ {
8
+ @rbclasses[this->class = copyinstr(arg0)] = count();
9
+ this->sep = strjoin(this->class, "#");
10
+ @rbmethods[strjoin(this->sep, copyinstr(arg1))] = count();
11
+ }
12
+
13
+ syscall:::entry
14
+ /pid == $1/
15
+ {
16
+ @syscalls[probefunc] = count();
17
+ }
18
+
19
+ END
20
+ {
21
+ printf("MySQL Queries");
22
+ printa(@queries);
23
+ printf("System Calls");
24
+ printa(@syscalls);
25
+ printf("Ruby Classes");
26
+ printa(@rbclasses);
27
+ printf("Ruby Methods");
28
+ printa(@rbmethods);
29
+ }
@@ -1,56 +1,26 @@
1
1
  <style type="text/css">
2
-
2
+ div#dtrace-report { background-color: #ddd; padding: 1em; margin: 1em; }
3
+ table.dtrace-report { background-color: #eee }
3
4
  </style>
4
5
 
6
+ <% if controller.dtrace_report.length > 0 %>
5
7
  <div id="dtrace-report">
6
- <h2>DTrace Report</h2>
7
-
8
- <% if controller.dtrace_report['queries'] %>
9
- <h3>MySQL queries</h3>
10
- <table class="report">
11
- <% controller.dtrace_report['queries'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
12
- <tr>
13
- <td><%= e[0] %></td>
14
- <td><%= e[1] %></td>
15
- </tr>
16
- <% end %>
17
- </table>
18
- <% end %>
19
-
20
- <% if controller.dtrace_report['syscalls'] %>
21
- <h3>System calls</h3>
22
- <table class="report">
23
- <% controller.dtrace_report['syscalls'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
24
- <tr>
25
- <td><%= e[0] %></td>
26
- <td><%= e[1] %></td>
27
- </tr>
28
- <% end %>
29
- </table>
30
- <% end %>
31
-
32
- <% if controller.dtrace_report['rbclasses'] %>
33
- <h3>Ruby classes instantiated</h3>
34
- <table class="report">
35
- <% controller.dtrace_report['rbclasses'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
8
+ <h2>DTrace Report: <%=h controller.dtrace_script %></h2>
9
+ <% controller.dtrace_report.each do |r| %>
10
+ <table class="dtrace-report">
11
+ <% if r.respond_to? :data %>
12
+ <% r.data.sort {|a,b| b.value <=> a.value }.each do |agg| %>
36
13
  <tr>
37
- <td><%= e[0] %></td>
38
- <td><%= e[1] %></td>
14
+ <td><%=h agg.tuple %></td>
15
+ <td><%=h agg.value %></td>
39
16
  </tr>
40
17
  <% end %>
41
- </table>
42
- <% end %>
43
-
44
- <% if controller.dtrace_report['rbmethods'] %>
45
- <h3>Ruby methods called</h3>
46
- <table class="report">
47
- <% controller.dtrace_report['rbmethods'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
48
- <tr>
49
- <td><%= e[0] %></td>
50
- <td><%= e[1] %></td>
51
- </tr>
18
+ <% else %>
19
+ <% if r.value != 0 %>
20
+ <h3><%=h r.value %></h3>
21
+ <% end %>
52
22
  <% end %>
53
23
  </table>
54
24
  <% end %>
55
-
56
25
  </div>
26
+ <% end %>
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: ruby-dtrace
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.3
7
- date: 2008-01-08 00:00:00 +00:00
6
+ version: 0.0.4
7
+ date: 2008-01-14 00:00:00 +00:00
8
8
  summary: Ruby bindings for libdtrace
9
9
  require_paths:
10
10
  - lib
@@ -64,10 +64,11 @@ files:
64
64
  - plugin/dtrace/init.rb
65
65
  - plugin/dtrace/lib/dtrace_helper.rb
66
66
  - plugin/dtrace/lib/dtrace_report.rb
67
- - plugin/dtrace/lib/dtrace_tracer.rb
68
67
  - plugin/dtrace/lib/dtracer.rb
69
68
  - plugin/dtrace/lib/dtracer_client.rb
70
69
  - plugin/dtrace/public/stylesheets/dtrace.css
70
+ - plugin/dtrace/scripts/default.d
71
+ - plugin/dtrace/scripts/rails_mysql.d
71
72
  - plugin/dtrace/tasks/dtrace.rake
72
73
  - plugin/dtrace/test/dtrace_test.rb
73
74
  - plugin/dtrace/views/dtrace/_report.rhtml
@@ -1,34 +0,0 @@
1
- require 'dtrace'
2
-
3
- class Dtracer
4
-
5
- def start_dtrace(pid)
6
- @d = Dtrace.new
7
- @d.setopt("aggsize", "4m")
8
- @d.setopt("bufsize", "4m")
9
- progtext = 'ruby$1:::function-entry{ @[strjoin(strjoin(copyinstr(arg0),"."),copyinstr(arg1))] = count(); }'
10
- begin
11
- prog = @d.compile(progtext, pid.to_s)
12
- prog.execute
13
- @d.go
14
- rescue DtraceException => e
15
- puts "start: #{e.message}"
16
- end
17
- end
18
-
19
- def end_dtrace(pid)
20
- begin
21
- @d.stop
22
- @d.aggregate_snap
23
-
24
- dtrace_report = Hash.new
25
- @d.each_aggregate do |agg|
26
- dtrace_report[agg[1].data] = agg[2].data
27
- end
28
- rescue DtraceException => e
29
- puts "end: #{e.message}"
30
- end
31
-
32
- return dtrace_report
33
- end
34
- end