diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb index de971a3b09..023d1165b4 100644 --- a/test/dtrace/helper.rb +++ b/test/dtrace/helper.rb @@ -10,6 +10,22 @@ 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' + login = Etc.getlogin + ok = Etc.getgrnam('stapusr').mem.include?(login) && + Etc.getgrnam('stapdev').mem.include?(login) + rescue LoadError, ArgumentError + end unless ok +end + if ok case RUBY_PLATFORM when /darwin/i @@ -20,7 +36,24 @@ if ok 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 +ok &= system(*cmd, err: IO::NULL, out: IO::NULL) if cmd + module DTrace class TestCase < Test::Unit::TestCase INCLUDE = File.expand_path('..', File.dirname(__FILE__)) @@ -40,17 +73,52 @@ module DTrace end 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] READ_PROBES ||= proc do |cmd| IO.popen(cmd, err: [:child, :out], &:readlines) end - miniruby = "miniruby#{RbConfig::CONFIG["EXEEXT"]}" - miniruby = File.join(File.dirname(EnvUtil.rubybin), miniruby) - RUBYBIN = File.exist?(miniruby) ? miniruby : EnvUtil.rubybin - def trap_probe d_program, ruby_program + if Hash === d_program + d_program = d_program[IMPL] or + skip "#{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 @@ -62,7 +130,11 @@ module DTrace d_path = d.path rb_path = rb.path cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}" - cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ] + 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] @@ -81,4 +153,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