ruby-dtrace 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,9 @@
1
- == 0.0.4 / 2008-01-24
1
+ == 0.0.6 / 2008-02-17
2
+
3
+ * Add Dtrace::Provider, which allows creation of USDT providers from
4
+ Ruby, without the need to patch the interpreter.
5
+
6
+ == 0.0.5 / 2008-01-24
2
7
 
3
8
  * Add DtraceErrData and DtraceDropData for access to error
4
9
  and drop information
@@ -19,6 +19,11 @@ ext/dtrace_recdesc.c
19
19
  ext/dtrace_util.c
20
20
  ext/extconf.rb
21
21
  lib/dtrace.rb
22
+ lib/dtrace/probe.rb
23
+ lib/dtrace/provider.rb
24
+ lib/dtrace/provider/osx.rb
25
+ lib/dtrace/provider/solaris.rb
26
+ lib/dtrace/tracer.rb
22
27
  lib/dtraceaggregate.rb
23
28
  lib/dtraceaggregateset.rb
24
29
  lib/dtraceconsumer.rb
@@ -50,3 +55,4 @@ test/test_dtrace_profile.rb
50
55
  test/test_dtrace_repeat.rb
51
56
  test/test_dtrace_rubyprobe.rb
52
57
  test/test_dtrace_typefilter.rb
58
+ test/test_dynusdt.rb
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  #include "dtrace_api.h"
6
+ #include <ctype.h>
6
7
 
7
8
  RUBY_EXTERN VALUE eDtraceException;
8
9
 
@@ -63,7 +63,7 @@ require 'dtracedata'
63
63
  # end
64
64
 
65
65
  class Dtrace
66
- VERSION = '0.0.5'
66
+ VERSION = '0.0.6'
67
67
 
68
68
  STATUS_NONE = 0
69
69
  STATUS_OKAY = 1
@@ -0,0 +1,49 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ class Dtrace
7
+ # DTrace::Probe - Using dynamically created USDT probes in Ruby
8
+ # programs:
9
+ #
10
+ # Having created the following probes with Dtrace::Provider:
11
+ #
12
+ # 74777 action_controller12297 action_controller.so process_finish process-finish
13
+ # 74778 action_controller12297 action_controller.so process_start process-start
14
+ #
15
+ # you can fire them with the following Ruby statements:
16
+ #
17
+ # Dtrace::Probe::ActionController.process_start do |p|
18
+ # p.fire(request.url)
19
+ # end
20
+ #
21
+ # Note that the generated class corresponding to the provider is
22
+ # simply the provider class, camelized.
23
+ #
24
+ # The generated method corresponding to the probe name (with -
25
+ # replaced by _) yields a probe object, on which you can call fire(),
26
+ # passing arguments of the appropriate types -- you are responsible
27
+ # for any type conversions necessary.
28
+ #
29
+ # fire() takes as many arguments as you defined for the probe: if
30
+ # you have generated a list of arguments to pass to fire(), use the
31
+ # splat operator to expand the list:
32
+ #
33
+ # Dtrace::Probe::MyProvider.my_probe do |p|
34
+ # args_list = [ some operation to get a list ]
35
+ # p.fire(*args_list)
36
+ # end
37
+ #
38
+ # This yield/fire() syntax exposes the is-enabled feature of the
39
+ # generated USDT probes: if the probe is not enabled, then the yield
40
+ # does not happen: this allows you to put relatively expensive work
41
+ # in the block, and know it is only called if the probe is enabled.
42
+ # This way, the probe-disabled overhead of these providers is
43
+ # reduced to a single method call, to a C-implemented method which
44
+ # simply wraps the DTrace IS_ENABLED() macro for the probe.
45
+ #
46
+ class Probe
47
+ end
48
+ end
49
+
@@ -0,0 +1,236 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'dtrace/probe'
7
+ require 'dtrace/provider/solaris'
8
+ require 'dtrace/provider/osx'
9
+ require 'pathname'
10
+ require 'tempfile'
11
+
12
+ DTRACE = '/usr/sbin/dtrace'
13
+
14
+ class Dtrace
15
+
16
+ # A DTrace provider. Allows creation of USDT probes on a running
17
+ # Ruby program, by dynamically creating an extension module
18
+ # implementing the probes, and compiling and loading it. You can use
19
+ # this with a Ruby interpreter compiled with the core DTrace probes,
20
+ # but you don't have to.
21
+ #
22
+ # This requires the DTrace and Ruby toolchains to be available:
23
+ # dtrace(1M), and the compiler and linker used to build Ruby. The
24
+ # process is similar to RubyInline, but the actual RubyInline
25
+ # library is not required (the build process for DTrace USDT probes
26
+ # is sufficiently differnent to a standard Ruby extension that it's
27
+ # not worth using it).
28
+ #
29
+ # Both Solaris and OSX 10.5 are supported. Other DTrace-supporting
30
+ # platforms can be added by creating a new class under
31
+ # Dtrace::Provider and implementing or overriding the required steps
32
+ # in the build process.
33
+ #
34
+ # Firing probes is explained in Dtrace::Probe.
35
+ #
36
+ # There are some limitations:
37
+ #
38
+ # You cannot choose all the components of the probe name: you can
39
+ # choose the provider and probe name, but the module and function
40
+ # components will be derived by DTrace, and won't be meaningful
41
+ # (they'll refer to the shim extension that gets created, not to
42
+ # anything in your Ruby program). It seems unlikely it's possible to
43
+ # change this.
44
+ #
45
+ # You cannot currently set D attributes: they're hardcoded to a
46
+ # default set. This will change.
47
+ #
48
+ # The extension will currently be rebuilt every time the provider is
49
+ # created, as there's not yet any support for packaging the provider
50
+ # in some way. This will change, to something along the lines of
51
+ # what RubyInline does to allow a pre-built extension to be used.
52
+ #
53
+ class Provider
54
+
55
+ class BuildError < StandardError; end
56
+
57
+ # Creates a DTrace provider. Causes a shim extension to be built
58
+ # and loaded, implementing the probes.
59
+ #
60
+ # Example:
61
+ #
62
+ # Dtrace::Provider.create :action_controller do |p|
63
+ # p.probe :process_start, :string
64
+ # p.probe :process_finish, :string, :integer
65
+ # end
66
+ #
67
+ # The symbol passed to create becomes the name of the provider,
68
+ # and the class exposed under Dtrace::Probe in Ruby (camelized, so
69
+ # the above statement creates Dtrace::Probe::ActionController).
70
+ #
71
+ # create yields a Provider for the current platform, on which you
72
+ # can call probe, to create the individual probes.
73
+ #
74
+ def self.create(name)
75
+ if RUBY_PLATFORM =~ /darwin/
76
+ provider = Dtrace::Provider::OSX.new(name)
77
+ else
78
+ provider = Dtrace::Provider::Solaris.new(name)
79
+ end
80
+ yield provider
81
+ provider.enable
82
+ end
83
+
84
+ # Creates a DTrace USDT probe. Arguments are the probe name, and
85
+ # then the argument types it will accept. The following argument
86
+ # types are supported:
87
+ #
88
+ # :string (char *)
89
+ # :integer (int)
90
+ #
91
+ # The probe will be named based on the provider name and the
92
+ # probe's name:
93
+ #
94
+ # provider_name:provider_name.so:probe_name:probe-name
95
+ #
96
+ # See the limitations explained elsewhere for an explanation of
97
+ # this redundancy in probe names.
98
+ #
99
+ def probe(name, *types)
100
+ typemap = { :string => 'char *', :integer => 'int' }
101
+ @probes[name] = types.map {|t| typemap[t]}
102
+ end
103
+
104
+ def initialize(name)
105
+ @name = name.to_s
106
+ @class = camelize(name)
107
+ @probes = {}
108
+ end
109
+
110
+ def enable
111
+ Tempfile.open("dtrace_probe_#{@name}") do |f|
112
+ p = Pathname.new(f.path)
113
+ @tempdir = "#{p.dirname}/#{@name}"
114
+ begin
115
+ Dir.mkdir @tempdir
116
+ rescue Errno::EEXIST
117
+ nil
118
+ end
119
+
120
+ # Probe setup is split up for easy overriding
121
+ definition
122
+ header
123
+ source
124
+ ruby_object
125
+ dtrace_object
126
+ link
127
+ load
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ def run(cmd)
134
+ result = `#{cmd}`
135
+ if $? != 0
136
+ raise BuildError.new("Error running:\n#{cmd}\n\n#{result}")
137
+ end
138
+ end
139
+
140
+ def hdrdir
141
+ %w(srcdir archdir).map { |name|
142
+ dir = Config::CONFIG[name]
143
+ }.find { |dir|
144
+ dir and File.exist? File.join(dir, "/ruby.h")
145
+ } or abort "ERROR: Can't find header dir for ruby. Exiting..."
146
+ end
147
+
148
+ private
149
+
150
+ def camelize(lower_case_and_underscored_word)
151
+ # Pinched from ActiveSupport's Inflector
152
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
153
+ end
154
+
155
+ # compose provider definition .d file
156
+ def definition
157
+ stability = <<EOS
158
+ #pragma D attributes Evolving/Evolving/Common provider #{@name} provider
159
+ #pragma D attributes Private/Private/Common provider #{@name} module
160
+ #pragma D attributes Private/Private/Common provider #{@name} function
161
+ #pragma D attributes Evolving/Evolving/Common provider #{@name} name
162
+ #pragma D attributes Evolving/Evolving/Common provider #{@name} args
163
+ EOS
164
+ File.open("#{@tempdir}/probes.d", 'w') do |io|
165
+ io << "provider #{@name} {\n"
166
+ @probes.each_pair do |name, types|
167
+ probename = name.to_s.gsub(/_/, '__')
168
+ typesdesc = types.join(', ')
169
+ probedesc = " probe #{probename}(#{typesdesc});\n"
170
+ io << probedesc
171
+ end
172
+ io << "\n};\n\n#{stability}"
173
+ end
174
+ end
175
+
176
+ def header
177
+ run "#{DTRACE} -h -s #{@tempdir}/probes.d -o #{@tempdir}/probes.h"
178
+ end
179
+
180
+ # Generate the C source for the provider class
181
+ def source
182
+ rb2c = { 'char *' => 'STR2CSTR', 'int' => 'FIX2INT' }
183
+
184
+ File.open("#{@tempdir}/probes.c", 'w') do |io|
185
+ io.puts '#include "ruby.h"'
186
+ io.puts "#include \"#{@tempdir}/probes.h\""
187
+
188
+ @probes.each_pair do |name, types|
189
+ defn_args = []
190
+ call_args = []
191
+ types.each_with_index { |type, i| defn_args << "#{type} arg#{i}" }
192
+ types.each_with_index { |type, i| call_args << "#{rb2c[type]}(rb_ary_entry(args, #{i}))" }
193
+
194
+ io.puts <<EOC
195
+ static VALUE #{name}(VALUE self) {
196
+ if (#{@name.upcase}_#{name.to_s.upcase}_ENABLED()) {
197
+ VALUE args = rb_yield(self);
198
+ #{@name.upcase}_#{name.to_s.upcase}(#{call_args.join(', ')});
199
+ }
200
+ return Qnil;
201
+ }
202
+ EOC
203
+ end
204
+ io.puts <<EOC
205
+ static VALUE fire(VALUE self, VALUE args) {
206
+ return args;
207
+ }
208
+
209
+ void Init_#{@name}() {
210
+ VALUE c = rb_cObject;
211
+ rb_define_method(c, "fire", (VALUE(*)(ANYARGS))fire, -2);
212
+ EOC
213
+
214
+ @probes.each_pair do |name, types|
215
+ io.puts " rb_define_singleton_method(c, \"#{name}\", (VALUE(*)(ANYARGS))#{name}, 0);"
216
+ end
217
+
218
+ io.puts '}'
219
+ end
220
+ end
221
+
222
+ def load
223
+ # Load the generated extension with a full path (saves adjusting
224
+ # $:) Done in the context of an anonymous class, since the
225
+ # module does not itself define a class. TODO: find a way of
226
+ # doing this without string eval...
227
+ lib = "#{@tempdir}/#{@name}"
228
+ c = Class.new
229
+ c.module_eval do
230
+ require lib
231
+ end
232
+ eval "Dtrace::Probe::#{@class} = c"
233
+ end
234
+
235
+ end
236
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ class Dtrace
7
+ class Provider
8
+ class OSX < Provider
9
+
10
+ # build the .bundle
11
+ def ruby_object
12
+ run "#{Config::CONFIG['LDSHARED']} -I #{hdrdir} -o #{@tempdir}/#{@name}.bundle #{@tempdir}/probes.c"
13
+ end
14
+
15
+ def dtrace_object
16
+ # no-op on OSX
17
+ end
18
+
19
+ def link
20
+ # no-op on OSX
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'rbconfig'
7
+
8
+ class Dtrace
9
+ class Provider
10
+ class Solaris < Provider
11
+
12
+ # build the .o
13
+ def ruby_object
14
+ run "#{Config::CONFIG['CC']} -I#{hdrdir} -o #{@tempdir}/#{@name}.o -c #{@tempdir}/probes.c"
15
+ end
16
+
17
+ # build the dtrace.o (dtrace -G)
18
+ def dtrace_object
19
+ run "/usr/sbin/dtrace -G -s #{@tempdir}/probes.d -o #{@tempdir}/probes.o #{@tempdir}/#{@name}.o"
20
+ end
21
+
22
+ # build the .so
23
+ def link
24
+ run "#{Config::CONFIG['CC']} -shared -o #{@tempdir}/#{@name}.so #{@tempdir}/#{@name}.o #{@tempdir}/probes.o"
25
+ end
26
+
27
+ end
28
+ end
29
+ 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
@@ -5,6 +5,7 @@ module DtraceReport
5
5
 
6
6
  def self.included(base)
7
7
  base.extend DtraceMacro
8
+ @@tracer = nil
8
9
  end
9
10
 
10
11
  module DtraceMacro
@@ -43,19 +44,23 @@ module DtraceReport
43
44
  end
44
45
 
45
46
  def enable_dtrace
46
- @@tracer.start_dtrace($$)
47
+ if @@tracer
48
+ @@tracer.start_dtrace($$)
49
+ end
47
50
  end
48
51
 
49
52
  def append_dtrace_report
50
- @dtrace_script = @@tracer.script
51
- @dtrace_report = @@tracer.end_dtrace
52
- # yuck!
53
- old_template_root = @template.base_path
54
- begin
55
- @template.view_paths = File.expand_path(File.dirname(__FILE__) + '/../views')
56
- response.body.gsub!(/<\/body/, @template.render(:partial => 'dtrace/report') + '</body')
57
- ensure
58
- @template.view_paths = old_template_root
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
59
64
  end
60
65
  end
61
66
 
@@ -0,0 +1,135 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ require 'dtrace'
7
+ require 'dtrace/provider'
8
+ require 'test/unit'
9
+ require 'rbconfig'
10
+
11
+ # Tests for the Dtrace "dynamic USDT" library
12
+
13
+ class TestDynUsdt < Test::Unit::TestCase
14
+
15
+ def test_create_usdt
16
+ Dtrace::Provider.create :test_provider_create_usdt do |p|
17
+ p.probe :test_probe, :string
18
+ end
19
+
20
+ t = Dtrace.new
21
+ t.setopt("bufsize", "4m")
22
+
23
+ progtext = 'test_provider_create_usdt*:::test-probe { trace(copyinstr(arg0)); }'
24
+
25
+ prog = t.compile progtext
26
+ prog.execute
27
+ t.go
28
+
29
+ c = DtraceConsumer.new(t)
30
+
31
+ Dtrace::Probe::TestProviderCreateUsdt.test_probe do |p|
32
+ p.fire "test_argument"
33
+ end
34
+
35
+ i = 0
36
+ c.consume_once do |d|
37
+ i = i + 1
38
+ assert d
39
+ assert_not_nil d.probe
40
+ assert_equal "test_provider_create_usdt#{$$.to_s}:test_provider_create_usdt.#{Config::CONFIG['DLEXT']}:test_probe:test-probe", d.probe.to_s
41
+ assert_equal 1, d.data.length
42
+ assert_equal "test_argument", d.data[0].value
43
+ end
44
+ assert_equal 1, i
45
+ end
46
+
47
+ def test_create_usdt_argtypes
48
+ Dtrace::Provider.create :test_provider_create_usdt_argtypes do |p|
49
+ p.probe :test_probe_string, :string
50
+ p.probe :test_probe_int, :integer
51
+ end
52
+
53
+ t = Dtrace.new
54
+ t.setopt("bufsize", "4m")
55
+
56
+ progtext = <<EOD
57
+ test_provider_create_usdt_argtypes*:::test-probe-string { trace(copyinstr(arg0)); }
58
+ test_provider_create_usdt_argtypes*:::test-probe-int { trace(arg0); }
59
+ EOD
60
+
61
+ prog = t.compile progtext
62
+ prog.execute
63
+ t.go
64
+
65
+ c = DtraceConsumer.new(t)
66
+
67
+ Dtrace::Probe::TestProviderCreateUsdtArgtypes.test_probe_string do |p|
68
+ p.fire "test_argument"
69
+ end
70
+
71
+ Dtrace::Probe::TestProviderCreateUsdtArgtypes.test_probe_int do |p|
72
+ p.fire 42
73
+ end
74
+
75
+ i = 0
76
+ c.consume_once do |d|
77
+ assert d
78
+ assert_not_nil d.probe
79
+ if d.probe.name == 'test-probe-string'
80
+ assert_equal "test_argument", d.data[0].value
81
+ i = i + 1
82
+ elsif d.probe.name == 'test-probe-int'
83
+ assert_equal 42, d.data[0].value
84
+ i = i + 1
85
+ end
86
+ end
87
+ assert_equal 2, i
88
+ end
89
+
90
+ def test_create_usdt_manyargs
91
+ Dtrace::Provider.create :test_provider_create_usdt_manyargs do |p|
92
+ p.probe :test_probe, :string, :string, :string, :string, :string, :string, :string, :string
93
+ end
94
+
95
+ t = Dtrace.new
96
+ t.setopt("bufsize", "4m")
97
+
98
+ progtext = <<EOD
99
+ test_provider_create_usdt_manyargs*:::test-probe
100
+ {
101
+ trace(copyinstr(arg0));
102
+ trace(copyinstr(arg1));
103
+ trace(copyinstr(arg2));
104
+ trace(copyinstr(arg3));
105
+ trace(copyinstr(arg4));
106
+ trace(copyinstr(arg5));
107
+ trace(copyinstr(arg6));
108
+ trace(copyinstr(arg7));
109
+ }
110
+ EOD
111
+
112
+ prog = t.compile progtext
113
+ prog.execute
114
+ t.go
115
+
116
+ c = DtraceConsumer.new(t)
117
+
118
+ Dtrace::Probe::TestProviderCreateUsdtManyargs.test_probe do |p|
119
+ values_list = (0..7).map {|i| "value #{i.to_s}" }
120
+ p.fire(*values_list)
121
+ end
122
+
123
+ i = 0
124
+ c.consume_once do |d|
125
+ i = i + 1
126
+ assert_equal 8, d.data.length
127
+ (0..7).each do |j|
128
+ assert_equal "value #{j.to_s}", d.data[j].value
129
+ end
130
+ end
131
+ assert_equal 1, i
132
+ end
133
+
134
+ end
135
+
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.5
7
- date: 2008-01-24 00:00:00 +00:00
6
+ version: 0.0.6
7
+ date: 2008-02-17 00:00:00 +00:00
8
8
  summary: Ruby bindings for libdtrace
9
9
  require_paths:
10
10
  - lib
@@ -51,6 +51,11 @@ files:
51
51
  - ext/dtrace_util.c
52
52
  - ext/extconf.rb
53
53
  - lib/dtrace.rb
54
+ - lib/dtrace/probe.rb
55
+ - lib/dtrace/provider.rb
56
+ - lib/dtrace/provider/osx.rb
57
+ - lib/dtrace/provider/solaris.rb
58
+ - lib/dtrace/tracer.rb
54
59
  - lib/dtraceaggregate.rb
55
60
  - lib/dtraceaggregateset.rb
56
61
  - lib/dtraceconsumer.rb
@@ -82,6 +87,7 @@ files:
82
87
  - test/test_dtrace_repeat.rb
83
88
  - test/test_dtrace_rubyprobe.rb
84
89
  - test/test_dtrace_typefilter.rb
90
+ - test/test_dynusdt.rb
85
91
  test_files:
86
92
  - test/test_dtrace.rb
87
93
  - test/test_dtrace_aggregates.rb
@@ -91,6 +97,7 @@ test_files:
91
97
  - test/test_dtrace_repeat.rb
92
98
  - test/test_dtrace_rubyprobe.rb
93
99
  - test/test_dtrace_typefilter.rb
100
+ - test/test_dynusdt.rb
94
101
  rdoc_options:
95
102
  - --main
96
103
  - README.txt