ruby-dtrace 0.0.5 → 0.0.6

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,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