summaryrefslogtreecommitdiff
path: root/test/dtrace
diff options
context:
space:
mode:
Diffstat (limited to 'test/dtrace')
-rw-r--r--test/dtrace/helper.rb106
-rw-r--r--test/dtrace/test_array_create.rb8
-rw-r--r--test/dtrace/test_function_entry.rb9
-rw-r--r--test/dtrace/test_hash_create.rb6
-rw-r--r--test/dtrace/test_method_cache.rb2
-rw-r--r--test/dtrace/test_require.rb6
-rw-r--r--test/dtrace/test_singleton_function.rb9
-rw-r--r--test/dtrace/test_string.rb6
8 files changed, 119 insertions, 33 deletions
diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb
index 539cce9d6f..9e8c7ecd52 100644
--- a/test/dtrace/helper.rb
+++ b/test/dtrace/helper.rb
@@ -10,26 +10,62 @@ elsif (sudo = ENV["SUDO"]) and !sudo.empty? and (`#{sudo} echo ok` rescue false)
else
ok = false
end
+
+impl = :dtrace
+
+# GNU/Linux distros with Systemtap support allows unprivileged users
+# in the stapusr and statdev groups to work.
+if RUBY_PLATFORM =~ /linux/
+ impl = :stap
+ begin
+ require 'etc'
+ ok = (%w[stapusr stapdev].map {|g|(Etc.getgrnam(g) || raise(ArgumentError)).gid} & Process.groups).size == 2
+ rescue LoadError, ArgumentError
+ end unless ok
+end
+
if ok
case RUBY_PLATFORM
when /darwin/i
begin
require 'pty'
rescue LoadError
- ok = false
end
end
end
-ok &= (`dtrace -V` rescue false)
+
+# use miniruby to reduce the amount of trace data we don't care about
+rubybin = "miniruby#{RbConfig::CONFIG["EXEEXT"]}"
+rubybin = File.join(File.dirname(EnvUtil.rubybin), rubybin)
+rubybin = EnvUtil.rubybin unless File.executable?(rubybin)
+
+# make sure ruby was built with --enable-dtrace and we can run
+# dtrace(1) or stap(1):
+cmd = "#{rubybin} --disable=gems -eexit"
+case impl
+when :dtrace; cmd = %W(dtrace -l -n ruby$target:::gc-sweep-end -c #{cmd})
+when :stap; cmd = %W(stap -l process.mark("gc__sweep__end") -c #{cmd})
+else
+ warn "don't know how to check if built with #{impl} support"
+ cmd = false
+end
+
+NEEDED_ENVS = [RbConfig::CONFIG["LIBPATHENV"], "RUBY", "RUBYOPT"].compact
+
+if cmd and ok
+ sudocmd = []
+ if sudo
+ sudocmd << sudo
+ NEEDED_ENVS.each {|name| val = ENV[name] and sudocmd << "#{name}=#{val}"}
+ end
+ ok = system(*sudocmd, *cmd, err: IO::NULL, out: IO::NULL)
+end
+
module DTrace
class TestCase < Test::Unit::TestCase
INCLUDE = File.expand_path('..', File.dirname(__FILE__))
- case RUBY_PLATFORM
- when /solaris/i
- # increase bufsize to 8m (default 4m on Solaris)
- DTRACE_CMD = %w[dtrace -b 8m]
- when /darwin/i
+ if RUBY_PLATFORM =~ /darwin/i
READ_PROBES = proc do |cmd|
lines = nil
PTY.spawn(*cmd) do |io, _, pid|
@@ -37,7 +73,40 @@ module DTrace
Process.wait(pid)
end
lines
+ end if defined?(PTY)
+ end
+
+ # only handles simple cases, use a Hash for d_program
+ # if there are more complex cases
+ def dtrace2systemtap(d_program)
+ translate = lambda do |str|
+ # dtrace starts args with '0', systemtap with '1' and prefixes '$'
+ str = str.gsub(/\barg(\d+)/) { "$arg#{$1.to_i + 1}" }
+ # simple function mappings:
+ str.gsub!(/\bcopyinstr\b/, 'user_string')
+ str.gsub!(/\bstrstr\b/, 'isinstr')
+ str
+ end
+ out = ''
+ cond = nil
+ d_program.split(/^/).each do |l|
+ case l
+ when /\bruby\$target:::([a-z-]+)/
+ name = $1.gsub(/-/, '__')
+ out << %Q{probe process.mark("#{name}")\n}
+ when %r{/(.+)/}
+ cond = translate.call($1)
+ when "{\n"
+ out << l
+ out << "if (#{cond}) {\n" if cond
+ when "}\n"
+ out << "}\n" if cond
+ out << l
+ else
+ out << translate.call(l)
+ end
end
+ out
end
DTRACE_CMD ||= %w[dtrace]
@@ -46,10 +115,13 @@ module DTrace
IO.popen(cmd, err: [:child, :out], &:readlines)
end
- exeext = Regexp.quote(RbConfig::CONFIG["EXEEXT"])
- RUBYBIN = EnvUtil.rubybin.sub(/\/ruby-runner(?=#{exeext}\z)/, '/miniruby')
-
def trap_probe d_program, ruby_program
+ if Hash === d_program
+ d_program = d_program[IMPL] or
+ omit "#{d_program} not implemented for #{IMPL}"
+ elsif String === d_program && IMPL == :stap
+ d_program = dtrace2systemtap(d_program)
+ end
d = Tempfile.new(%w'probe .d')
d.write d_program
d.flush
@@ -60,11 +132,15 @@ module DTrace
d_path = d.path
rb_path = rb.path
-
- cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", "#{RUBYBIN} -I#{INCLUDE} #{rb_path}"]
+ cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}"
+ if IMPL == :stap
+ cmd = %W(stap #{d_path} -c #{cmd})
+ else
+ cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ]
+ end
if sudo = @@sudo
- [RbConfig::CONFIG["LIBPATHENV"], "RUBY", "RUBYOPT"].each do |name|
- if name and val = ENV[name]
+ NEEDED_ENVS.each do |name|
+ if val = ENV[name]
cmd.unshift("#{name}=#{val}")
end
end
@@ -80,4 +156,6 @@ end if ok
if ok
DTrace::TestCase.class_variable_set(:@@sudo, sudo)
+ DTrace::TestCase.const_set(:IMPL, impl)
+ DTrace::TestCase.const_set(:RUBYBIN, rubybin)
end
diff --git a/test/dtrace/test_array_create.rb b/test/dtrace/test_array_create.rb
index 44d4657b61..1bf20085ba 100644
--- a/test/dtrace/test_array_create.rb
+++ b/test/dtrace/test_array_create.rb
@@ -14,11 +14,11 @@ module DTrace
end
def test_many_lit
- trap_probe(probe, '[1,2,3,4]') { |_,rbfile,saw|
- saw = saw.map(&:split).find_all { |num, file, line|
+ trap_probe(probe, '[1,2,3,4]') { |_,rbfile,orig|
+ saw = orig.map(&:split).find_all { |num, file, line|
file == rbfile && num == '4' && line == '1'
}
- assert_operator saw.length, :>, 0
+ assert_operator saw.length, :>, 0, orig
}
end
@@ -26,7 +26,7 @@ module DTrace
def probe type = 'array'
<<-eoprobe
ruby$target:::#{type}-create
-/arg1/
+/arg1 && arg2/
{
printf("%d %s %d\\n", arg0, copyinstr(arg1), arg2);
}
diff --git a/test/dtrace/test_function_entry.rb b/test/dtrace/test_function_entry.rb
index fc07ccc455..e2395ab15a 100644
--- a/test/dtrace/test_function_entry.rb
+++ b/test/dtrace/test_function_entry.rb
@@ -17,8 +17,8 @@ ruby$target:::method-entry
row.first == 'Foo' && row[1] == 'foo'
}
- assert_equal 10, foo_calls.length
- line = '2'
+ assert_equal 10, foo_calls.length, probes
+ line = '3'
foo_calls.each { |f| assert_equal line, f[3] }
foo_calls.each { |f| assert_equal rb_file, f[2] }
}
@@ -38,8 +38,8 @@ ruby$target:::method-return
row.first == 'Foo' && row[1] == 'foo'
}
- assert_equal 10, foo_calls.length
- line = '2'
+ assert_equal 10, foo_calls.length, probes.inspect
+ line = '3'
foo_calls.each { |f| assert_equal line, f[3] }
foo_calls.each { |f| assert_equal rb_file, f[2] }
}
@@ -77,6 +77,7 @@ ruby$target:::method-return
private
def ruby_program
<<-eoruby
+ TracePoint.new{}.__enable(nil, nil, Thread.current)
class Foo
def foo; end
end
diff --git a/test/dtrace/test_hash_create.rb b/test/dtrace/test_hash_create.rb
index 83a4d0062c..603ee21872 100644
--- a/test/dtrace/test_hash_create.rb
+++ b/test/dtrace/test_hash_create.rb
@@ -22,11 +22,11 @@ module DTrace
end
def test_hash_lit_elements
- trap_probe(probe, '{ :foo => :bar }') { |_,rbfile,saw|
- saw = saw.map(&:split).find_all { |num, file, line|
+ trap_probe(probe, '{ :foo => :bar }') { |_,rbfile,orig|
+ saw = orig.map(&:split).find_all { |num, file, line|
file == rbfile && num == '2'
}
- assert_operator saw.length, :>, 0
+ assert_operator saw.length, :>, 0, orig
}
end
diff --git a/test/dtrace/test_method_cache.rb b/test/dtrace/test_method_cache.rb
index a101b5ddec..88b927464a 100644
--- a/test/dtrace/test_method_cache.rb
+++ b/test/dtrace/test_method_cache.rb
@@ -19,7 +19,7 @@ module DTrace
def probe
<<-eoprobe
ruby$target:::method-cache-clear
-/arg1/
+/arg1 && arg2/
{
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
}
diff --git a/test/dtrace/test_require.rb b/test/dtrace/test_require.rb
index 9fa6c0e87c..da5c08f7fc 100644
--- a/test/dtrace/test_require.rb
+++ b/test/dtrace/test_require.rb
@@ -25,6 +25,12 @@ ruby$target:::require-return
printf("%s\\n", copyinstr(arg0));
}
eoprobe
+ trap_probe(probe, ruby_program) { |d_file, rb_file, saw|
+ required = saw.map { |s| s.split }.find_all do |(required, _)|
+ required == 'dtrace/dummy'
+ end
+ assert_equal 10, required.length
+ }
end
private
diff --git a/test/dtrace/test_singleton_function.rb b/test/dtrace/test_singleton_function.rb
index 3698a02c93..bad1fa0692 100644
--- a/test/dtrace/test_singleton_function.rb
+++ b/test/dtrace/test_singleton_function.rb
@@ -17,8 +17,8 @@ ruby$target:::method-entry
row.first == 'Foo' && row[1] == 'foo'
}
- assert_equal 10, foo_calls.length
- line = '2'
+ assert_equal 10, foo_calls.length, probes.inspect
+ line = '3'
foo_calls.each { |f| assert_equal line, f[3] }
foo_calls.each { |f| assert_equal rb_file, f[2] }
}
@@ -37,8 +37,8 @@ ruby$target:::method-return
row.first == 'Foo' && row[1] == 'foo'
}
- assert_equal 10, foo_calls.length
- line = '2'
+ assert_equal 10, foo_calls.length, probes.inspect
+ line = '3'
foo_calls.each { |f| assert_equal line, f[3] }
foo_calls.each { |f| assert_equal rb_file, f[2] }
}
@@ -46,6 +46,7 @@ ruby$target:::method-return
def ruby_program
<<-eoruby
+ TracePoint.new{}.__enable(nil, nil, Thread.current)
class Foo
def self.foo; end
end
diff --git a/test/dtrace/test_string.rb b/test/dtrace/test_string.rb
index 407280b1fc..a72f989f63 100644
--- a/test/dtrace/test_string.rb
+++ b/test/dtrace/test_string.rb
@@ -4,11 +4,11 @@ require_relative 'helper'
module DTrace
class TestStringProbes < TestCase
def test_object_create_start_string_lit
- trap_probe(probe, '"omglolwutbbq"') { |_,rbfile,saw|
- saw = saw.map(&:split).find_all { |klass, file, line, len|
+ trap_probe(probe, '"omglolwutbbq"') { |_,rbfile,orig|
+ saw = orig.map(&:split).find_all { |klass, file, line, len|
file == rbfile && len == '12' && line == '1'
}
- assert_equal(%w{ String }, saw.map(&:first))
+ assert_equal(%w{ String }, saw.map(&:first), orig.inspect)
assert_equal([rbfile], saw.map { |line| line[1] })
assert_equal(['1'], saw.map { |line| line[2] })
}