chrisa-ruby-dtrace 0.2.0

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.
Files changed (88) hide show
  1. data/History.txt +34 -0
  2. data/Manifest.txt +58 -0
  3. data/README.txt +88 -0
  4. data/Rakefile +73 -0
  5. data/examples/scsi.rb +442 -0
  6. data/ext/dof/constants.c +49 -0
  7. data/ext/dof/dof.h +55 -0
  8. data/ext/dof/dof_api.c +57 -0
  9. data/ext/dof/dof_helper.c +82 -0
  10. data/ext/dof/extconf.rb +4 -0
  11. data/ext/dof/file.c +56 -0
  12. data/ext/dof/generator.c +9 -0
  13. data/ext/dof/header.c +80 -0
  14. data/ext/dof/parser.c +415 -0
  15. data/ext/dof/parser.h +10 -0
  16. data/ext/dof/section.c +302 -0
  17. data/ext/dtrace_aggdata.c +142 -0
  18. data/ext/dtrace_api.c +119 -0
  19. data/ext/dtrace_api.h +150 -0
  20. data/ext/dtrace_bufdata.c +139 -0
  21. data/ext/dtrace_dropdata.c +131 -0
  22. data/ext/dtrace_errdata.c +110 -0
  23. data/ext/dtrace_hdl.c +577 -0
  24. data/ext/dtrace_probedata.c +267 -0
  25. data/ext/dtrace_probedesc.c +78 -0
  26. data/ext/dtrace_process.c +37 -0
  27. data/ext/dtrace_program.c +62 -0
  28. data/ext/dtrace_programinfo.c +60 -0
  29. data/ext/dtrace_recdesc.c +46 -0
  30. data/ext/dtrace_util.c +92 -0
  31. data/ext/extconf.rb +28 -0
  32. data/ext/stubs.txt +78 -0
  33. data/lib/dtrace/aggregate.rb +40 -0
  34. data/lib/dtrace/aggregateset.rb +19 -0
  35. data/lib/dtrace/consumer.rb +174 -0
  36. data/lib/dtrace/data.rb +82 -0
  37. data/lib/dtrace/dof/file.rb +63 -0
  38. data/lib/dtrace/dof/section/strtab.rb +21 -0
  39. data/lib/dtrace/dof/section.rb +69 -0
  40. data/lib/dtrace/dof.rb +8 -0
  41. data/lib/dtrace/printfrecord.rb +10 -0
  42. data/lib/dtrace/probe.rb +46 -0
  43. data/lib/dtrace/probedata.rb +23 -0
  44. data/lib/dtrace/probedesc.rb +15 -0
  45. data/lib/dtrace/provider/probedef.rb +24 -0
  46. data/lib/dtrace/provider.rb +231 -0
  47. data/lib/dtrace/record.rb +11 -0
  48. data/lib/dtrace/stackrecord.rb +31 -0
  49. data/lib/dtrace/tracer.rb +35 -0
  50. data/lib/dtrace.rb +74 -0
  51. data/lib/dtraceconsumer.rb +9 -0
  52. data/plugin/dtrace/README +81 -0
  53. data/plugin/dtrace/Rakefile +22 -0
  54. data/plugin/dtrace/bin/dtracer.rb +29 -0
  55. data/plugin/dtrace/init.rb +7 -0
  56. data/plugin/dtrace/lib/dtrace_helper.rb +2 -0
  57. data/plugin/dtrace/lib/dtrace_report.rb +67 -0
  58. data/plugin/dtrace/lib/dtracer.rb +52 -0
  59. data/plugin/dtrace/lib/dtracer_client.rb +26 -0
  60. data/plugin/dtrace/public/stylesheets/dtrace.css +48 -0
  61. data/plugin/dtrace/scripts/default.d +11 -0
  62. data/plugin/dtrace/scripts/rails_mysql.d +29 -0
  63. data/plugin/dtrace/tasks/dtrace.rake +52 -0
  64. data/plugin/dtrace/test/dtrace_test.rb +8 -0
  65. data/plugin/dtrace/views/dtrace/_report.rhtml +26 -0
  66. data/test/apple-dof +0 -0
  67. data/test/disabled_probe_effect.txt +19 -0
  68. data/test/dof +0 -0
  69. data/test/dof2 +0 -0
  70. data/test/test_disabled_probe_effect.rb +60 -0
  71. data/test/test_dof_generator.rb +142 -0
  72. data/test/test_dof_helper.rb +106 -0
  73. data/test/test_dof_parser.rb +25 -0
  74. data/test/test_dof_providers.rb +282 -0
  75. data/test/test_dof_strtabs.rb +92 -0
  76. data/test/test_dtrace.rb +111 -0
  77. data/test/test_dtrace_aggregates.rb +56 -0
  78. data/test/test_dtrace_drops_errors.rb +183 -0
  79. data/test/test_dtrace_probe.rb +383 -0
  80. data/test/test_dtrace_probes.rb +400 -0
  81. data/test/test_dtrace_processes.rb +83 -0
  82. data/test/test_dtrace_profile.rb +232 -0
  83. data/test/test_dtrace_provider.rb +153 -0
  84. data/test/test_dtrace_repeat.rb +51 -0
  85. data/test/test_dtrace_rubyprobe.rb +52 -0
  86. data/test/test_dtrace_typefilter.rb +108 -0
  87. data/test/test_legacy_consumer.rb +56 -0
  88. metadata +165 -0
@@ -0,0 +1,231 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2008 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'dtrace'
7
+ require 'dtrace/dof'
8
+ require 'dtrace/probe'
9
+ require 'dtrace/provider/probedef'
10
+
11
+ class Dtrace
12
+
13
+ # A DTrace provider. Allows creation of USDT probes on a running
14
+ # Ruby program. You can use this with a Ruby interpreter compiled
15
+ # with the core DTrace probes, but you don't have to.
16
+ #
17
+ # Firing probes is explained in Dtrace::Probe.
18
+ #
19
+ class Provider
20
+ include Dtrace::Dof::Constants
21
+
22
+ Typemap = { :string => 'char *', :integer => 'int' }
23
+
24
+ # Creates a DTrace provider.
25
+ #
26
+ # Example:
27
+ #
28
+ # Dtrace::Provider.create :action_controller do |p|
29
+ # p.probe :process_start, :string
30
+ # p.probe :process_finish, :string, :integer
31
+ # end
32
+ #
33
+ # The symbol passed to create becomes the name of the provider,
34
+ # and the class exposed under Dtrace::Probe in Ruby (camelized, so
35
+ # the above statement creates Dtrace::Probe::ActionController).
36
+ #
37
+ # create yields a Provider for the current platform, on which you
38
+ # can call probe, to create the individual probes.
39
+ #
40
+ # You can override the module name in the created probes, by
41
+ # passing in an hash:
42
+ #
43
+ # Dtrace::Provider.create :foo, { :module => 'somemodule' } do |p|
44
+ # p.probe...
45
+ # end
46
+ def self.create(name, options={})
47
+ options[:module] ||= 'ruby'
48
+ provider = Dtrace::Provider.new(name, options[:module])
49
+ yield provider
50
+ provider.enable
51
+ end
52
+
53
+ # Creates a DTrace USDT probe. Arguments are the probe name, and
54
+ # then the argument types it will accept. The following argument
55
+ # types are supported:
56
+ #
57
+ # :string (char *)
58
+ # :integer (int)
59
+ #
60
+ # Providing an options hash as the second argument allows you to
61
+ # override the function name, otherwise it will be taken from the
62
+ # caller of this function:
63
+ #
64
+ # p.probe :foo, { :function => 'somefunction' }, :int, ...
65
+ #
66
+ def probe(name, *types)
67
+ options = {}
68
+ if types[0].respond_to? :keys
69
+ options = types.shift
70
+ end
71
+ options[:function] ||= Kernel.caller[0].match(/`(.*)'/)[1]
72
+
73
+ pd = Dtrace::Provider::ProbeDef.new(name, options[:function])
74
+ types.each do |t|
75
+ if Typemap[t].nil?
76
+ raise Dtrace::Exception.new("type '#{t}' invalid")
77
+ else
78
+ pd.args << Typemap[t]
79
+ end
80
+ end
81
+
82
+ @probe_defs << pd
83
+ end
84
+
85
+ def initialize(provider_name, module_name)
86
+ @name = provider_name.to_s
87
+ @module = module_name.to_s
88
+ @class = camelize(provider_name)
89
+ @probe_defs = []
90
+ end
91
+
92
+ def enable
93
+ f = Dtrace::Dof::File.new
94
+
95
+ strtab = Dtrace::Dof::Section::Strtab.new(0)
96
+ f.sections << strtab
97
+
98
+ s = Dtrace::Dof::Section.new(DOF_SECT_PROBES, 1)
99
+ probes = Array.new
100
+ stubs = Hash.new
101
+ argidx = 0
102
+ offidx = 0
103
+ @probe_defs.each do |pd|
104
+ argc = pd.argc
105
+
106
+ argv = 0
107
+ pd.args.each do |type|
108
+ i = strtab.add(type)
109
+ argv = i if argv == 0
110
+ end
111
+
112
+ probe = Dtrace::Probe.new(argc)
113
+ probes <<
114
+ {
115
+ :name => strtab.add(pd.name),
116
+ :func => strtab.add(pd.function),
117
+ :noffs => 1,
118
+ :enoffidx => offidx,
119
+ :argidx => argidx,
120
+ :nenoffs => 1,
121
+ :offidx => offidx,
122
+ :addr => probe.addr,
123
+ :nargc => argc,
124
+ :xargc => argc,
125
+ :nargv => argv,
126
+ :xargv => argv,
127
+ }
128
+
129
+ stubs[pd.name] = probe
130
+ argidx += argc
131
+ offidx += 1
132
+ end
133
+ s.data = probes
134
+ f.sections << s
135
+
136
+ s = Dtrace::Dof::Section.new(DOF_SECT_PRARGS, 2)
137
+ s.data = Array.new
138
+ @probe_defs.each do |pd|
139
+ pd.args.each_with_index do |arg, i|
140
+ s.data << i
141
+ end
142
+ end
143
+ if s.data.empty?
144
+ s.data = [ 0 ]
145
+ end
146
+ f.sections << s
147
+
148
+ s = Dtrace::Dof::Section.new(DOF_SECT_PROFFS, 3)
149
+ s.data = Array.new
150
+ @probe_defs.each do |pd|
151
+ s.data << stubs[pd.name].probe_offset(f.addr, pd.argc)
152
+ end
153
+ if s.data.empty?
154
+ s.data = [ 0 ]
155
+ end
156
+ f.sections << s
157
+
158
+ s = Dtrace::Dof::Section.new(DOF_SECT_PRENOFFS, 4)
159
+ s.data = Array.new
160
+ @probe_defs.each do |pd|
161
+ s.data << stubs[pd.name].is_enabled_offset(f.addr)
162
+ end
163
+ if s.data.empty?
164
+ s.data = [ 0 ]
165
+ end
166
+ f.sections << s
167
+
168
+ s = Dtrace::Dof::Section.new(DOF_SECT_PROVIDER, 5)
169
+ s.data = {
170
+ :strtab => 0,
171
+ :probes => 1,
172
+ :prargs => 2,
173
+ :proffs => 3,
174
+ :prenoffs => 4,
175
+ :name => strtab.add(@name),
176
+ :provattr => {
177
+ :name => DTRACE_STABILITY_EVOLVING,
178
+ :data => DTRACE_STABILITY_EVOLVING,
179
+ :class => DTRACE_STABILITY_EVOLVING
180
+ },
181
+ :modattr => {
182
+ :name => DTRACE_STABILITY_PRIVATE,
183
+ :data => DTRACE_STABILITY_PRIVATE,
184
+ :class => DTRACE_STABILITY_EVOLVING
185
+ },
186
+ :funcattr => {
187
+ :name => DTRACE_STABILITY_PRIVATE,
188
+ :data => DTRACE_STABILITY_PRIVATE,
189
+ :class => DTRACE_STABILITY_EVOLVING
190
+ },
191
+ :nameattr => {
192
+ :name => DTRACE_STABILITY_EVOLVING,
193
+ :data => DTRACE_STABILITY_EVOLVING,
194
+ :class => DTRACE_STABILITY_EVOLVING
195
+ },
196
+ :argsattr => {
197
+ :name => DTRACE_STABILITY_EVOLVING,
198
+ :data => DTRACE_STABILITY_EVOLVING,
199
+ :class => DTRACE_STABILITY_EVOLVING
200
+ },
201
+ }
202
+ f.sections << s
203
+
204
+ f.generate
205
+ Dtrace::Dof.loaddof(f, @module)
206
+
207
+ c = Class.new
208
+ c.class_eval do
209
+ @@probes = stubs
210
+ def self.method_missing(name)
211
+ name = name.to_s
212
+ unless @@probes[name].nil?
213
+ if @@probes[name].is_enabled?
214
+ yield @@probes[name]
215
+ end
216
+ end
217
+ end
218
+ end
219
+ eval "Dtrace::Probe::#{@class} = c"
220
+
221
+ end
222
+
223
+ private
224
+
225
+ def camelize(lower_case_and_underscored_word)
226
+ # Pinched from ActiveSupport's Inflector
227
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
228
+ end
229
+
230
+ end
231
+ end
@@ -0,0 +1,11 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+ # A scalar DTrace record. Its value is as set by the DTrace action
6
+ # which triggered it.
7
+ class Dtrace
8
+ class Record
9
+ attr_accessor :value
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ # A record representing the result of a stack() or ustack()
7
+ # action. Its value is a list of symbolic stack frames:
8
+ #
9
+ # #<DtraceStackRecord:0x14e24 @value=
10
+ # ["libSystem.B.dylib`__sysctl+0xa",
11
+ # "libdtrace.dylib`dt_aggregate_go+0x9a",
12
+ # "dtrace_api.bundle`dtrace_hdl_go+0x30",
13
+ # "libruby.1.dylib`rb_eval_string_wrap+0x40fd",
14
+ # "libruby.1.dylib`rb_eval_string_wrap+0x4cdb",
15
+ # ...
16
+ # "libruby.1.dylib`rb_apply+0x392",
17
+ # "libruby.1.dylib`rb_eval_string_wrap+0xe82"]>
18
+ #
19
+ class Dtrace
20
+ class StackRecord
21
+ attr_reader :value
22
+
23
+ # Given a stack as a string returned from DTrace, set the value of
24
+ # this record to a list of stack frames.
25
+ def parse(raw)
26
+ frames = raw.split(/\n/)
27
+ @value = frames.map {|f| f.lstrip }.select {|f| f.length > 0 }
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ # Leopard's ruby-probe is DTracer, Solaris's is Tracer.
7
+
8
+ class Dtrace
9
+ class Tracer
10
+
11
+ class NullTracer
12
+ def self.fire(arg0, arg1)
13
+ puts "NullTracer: #{arg0} #{arg1}"
14
+ end
15
+ end
16
+
17
+ @@tracer = nil
18
+ def self.fire(*args)
19
+ if @@tracer == nil
20
+ begin
21
+ # Avoid getting ourselves here:
22
+ @@tracer = Module.const_get('Tracer')
23
+ rescue NameError
24
+ begin
25
+ @@tracer = DTracer
26
+ rescue NameError
27
+ @@tracer = Dtrace::Tracer::NullTracer
28
+ end
29
+ end
30
+ end
31
+ @@tracer.fire(*args)
32
+ end
33
+
34
+ end
35
+ end
data/lib/dtrace.rb ADDED
@@ -0,0 +1,74 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'dtrace_api'
7
+ require 'dtrace/record'
8
+ require 'dtrace/consumer'
9
+ require 'dtraceconsumer'
10
+ require 'dtrace/aggregate'
11
+ require 'dtrace/aggregateset'
12
+ require 'dtrace/probedata'
13
+ require 'dtrace/probedesc'
14
+ require 'dtrace/stackrecord'
15
+ require 'dtrace/printfrecord'
16
+ require 'dtrace/data'
17
+ require 'dtrace/version'
18
+
19
+ # A DTrace handle. Provides methods for inspecting available probes,
20
+ # compiling and running programs, and for setting up callbacks to
21
+ # receive trace data.
22
+ #
23
+ # The general structure of a Dtrace-based program is:
24
+ #
25
+ # * Create a handle with Dtrace.new
26
+ # * Set options
27
+ # * Compile the program, possibly inspecting the return Dtrace::ProgramInfo
28
+ # * Execute the program
29
+ # * Start tracing
30
+ # * Consume data, either directly by setting up callbacks, or using a Dtrace::Consumer.
31
+ # * Stop tracing
32
+ #
33
+ # === Listing probes
34
+ #
35
+ # d.each_probe do |p|
36
+ # puts "#{p.provider}:#{p.mod}:#{p.func}:#{p.name}"
37
+ # end
38
+ #
39
+ # === Setting options
40
+ #
41
+ # d.setopt("bufsize", "8m")
42
+ # d.setopt("aggsize", "4m")
43
+ # d.setopt("stackframes", "5")
44
+ # d.setopt("strsize", "131072")
45
+ #
46
+ # === Compiling a program
47
+ #
48
+ # d.compile "syscall:::entry { trace(execname); stack(); }"
49
+ # d.execute
50
+ #
51
+ # === Setting up callbacks
52
+ #
53
+ # d.buf_consumer(prob {|buf| yield buf })
54
+ # d.work(proc {|probe| yield probe }, proc {|rec| yield rec })
55
+ #
56
+ # === Creating a process
57
+ #
58
+ # p = t.createprocess([ '/usr/bin/true' ])
59
+ # t.go
60
+ # p.continue
61
+ #
62
+ # c = Dtrace::Consumer.new(t)
63
+ # c.consume do |d|
64
+ # ..
65
+ # end
66
+
67
+ class Dtrace
68
+ STATUS_NONE = 0
69
+ STATUS_OKAY = 1
70
+ STATUS_EXITED = 2
71
+ STATUS_FILLED = 3
72
+ STATUS_STOPPED = 4
73
+ end
74
+
@@ -0,0 +1,9 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'dtrace/consumer'
7
+
8
+ # Interface preservation - see Dtrace::Consumer.
9
+ class DtraceConsumer < Dtrace::Consumer; end
@@ -0,0 +1,81 @@
1
+ Dtrace
2
+ ======
3
+
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
+
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the dtrace plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the dtrace plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Dtrace'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'drb'
5
+ require 'pathname'
6
+
7
+ # allow running standalone or in a rails app
8
+ script_dir = Pathname.new(File.dirname(__FILE__)).realpath
9
+ rails_script = script_dir + '../vendor/plugins/dtrace/bin'
10
+ plugin_script = script_dir + '../../../../vendor/plugins/dtrace/bin'
11
+ if rails_script.directory?
12
+ rails_root = script_dir + '../'
13
+ elsif plugin_script.directory?
14
+ rails_root = script_dir + '../../../..'
15
+ end
16
+ $LOAD_PATH << File.join(rails_root + 'vendor/plugins/dtrace/lib')
17
+
18
+ require 'dtracer'
19
+
20
+ here = "druby://localhost:2999"
21
+ tracer = Dtracer.new
22
+ DRb.start_service here, tracer
23
+ puts "DTrace helper started"
24
+ begin
25
+ DRb.thread.join
26
+ rescue Interrupt
27
+ exit 0
28
+ end
29
+
@@ -0,0 +1,7 @@
1
+ require 'dtrace_report'
2
+ ActionController::Base.class_eval do
3
+ include DtraceReport
4
+ before_filter :enable_dtrace if RAILS_ENV == 'development'
5
+ after_filter :append_dtrace_report if RAILS_ENV == 'development'
6
+ end
7
+
@@ -0,0 +1,2 @@
1
+ module DtraceHelper
2
+ end
@@ -0,0 +1,67 @@
1
+ require 'dtracer'
2
+ require 'dtracer_client'
3
+
4
+ module DtraceReport
5
+
6
+ def self.included(base)
7
+ base.extend DtraceMacro
8
+ @@tracer = nil
9
+ end
10
+
11
+ module DtraceMacro
12
+ def dtrace(enable=:on, options={})
13
+ if enable == :on
14
+
15
+ # Set tracer type, in-process or helper-process
16
+ if options[:tracer] == :self
17
+ tracer = Dtracer.new
18
+ elsif options[:tracer] == :helper
19
+ tracer = DtracerClient.new
20
+ else
21
+ raise "tracer option is self or helper"
22
+ end
23
+
24
+ tracer.logger = logger
25
+
26
+ # Set script, or default
27
+ if options[:script]
28
+ tracer.script = options[:script]
29
+ else
30
+ tracer.script = 'default.d'
31
+ end
32
+
33
+ DtraceReport.tracer = tracer
34
+ end
35
+ end
36
+ end
37
+
38
+ attr_reader :dtrace_report
39
+ attr_reader :dtrace_script
40
+
41
+ protected
42
+ def self.tracer=(tracer)
43
+ @@tracer = tracer
44
+ end
45
+
46
+ def enable_dtrace
47
+ if @@tracer
48
+ @@tracer.start_dtrace($$)
49
+ end
50
+ end
51
+
52
+ def append_dtrace_report
53
+ if @@tracer
54
+ @dtrace_script = @@tracer.script
55
+ @dtrace_report = @@tracer.end_dtrace
56
+ # yuck!
57
+ old_template_root = @template.base_path
58
+ begin
59
+ @template.view_paths = File.expand_path(File.dirname(__FILE__) + '/../views')
60
+ response.body.gsub!(/<\/body/, @template.render(:partial => 'dtrace/report') + '</body')
61
+ ensure
62
+ @template.view_paths = old_template_root
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,52 @@
1
+ require 'dtrace'
2
+
3
+ class Dtracer
4
+ attr_writer :logger, :dprogram
5
+ attr_reader :script
6
+
7
+ def script=(script)
8
+ @script = script
9
+ scriptdir = File.expand_path(File.dirname(__FILE__) + "/../scripts")
10
+ @dprogram = IO.read("#{scriptdir}/#{script}")
11
+ end
12
+
13
+ def start_dtrace(pid)
14
+ begin
15
+ @d = Dtrace.new
16
+ @d.setopt("aggsize", "4m")
17
+ @d.setopt("bufsize", "4m")
18
+ rescue DtraceException => e
19
+ @logger.warn("DTrace start setup: #{e.message}")
20
+ return
21
+ end
22
+
23
+ begin
24
+ prog = @d.compile(@dprogram, pid.to_s)
25
+ prog.execute
26
+ @d.go
27
+ rescue DtraceException => e
28
+ @logger.warn("DTrace start compile: #{e.message}")
29
+ end
30
+ end
31
+
32
+ def end_dtrace
33
+ # Check presence of handle and correct status.
34
+ return [] unless @d && @d.status == Dtrace::STATUS_OKAY
35
+
36
+ dtrace_data = nil
37
+ begin
38
+ c = DtraceConsumer.new(@d)
39
+ c.consume_once do |d|
40
+ dtrace_data = d
41
+ end
42
+ rescue DtraceException => e
43
+ @logger.warn("DTrace end: #{e.message}")
44
+ end
45
+
46
+ if dtrace_data
47
+ return dtrace_data.data
48
+ else
49
+ return []
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ require 'drb'
2
+
3
+ class DtracerClient
4
+ attr_writer :logger
5
+ attr_reader :script
6
+
7
+ def initialize
8
+ DRb.start_service
9
+ @tracer = DRbObject.new(nil, 'druby://localhost:2999')
10
+ end
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
+
18
+ def start_dtrace(pid)
19
+ @tracer.start_dtrace(pid)
20
+ end
21
+
22
+ def end_dtrace
23
+ @tracer.end_dtrace
24
+ end
25
+
26
+ end