ruby-dtrace 0.0.3 → 0.0.4

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.
@@ -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