1 # -*- coding: us-ascii -*-
2 # frozen_string_literal: false
8 elsif (sudo = ENV["SUDO"]) and !sudo.empty? and (`#{sudo} echo ok` rescue false)
16 # GNU/Linux distros with Systemtap support allows unprivileged users
17 # in the stapusr and statdev groups to work.
18 if RUBY_PLATFORM =~ /linux/
22 ok = (%w[stapusr stapdev].map {|g|(Etc.getgrnam(g) || raise(ArgumentError)).gid} & Process.groups).size == 2
23 rescue LoadError, ArgumentError
37 # use miniruby to reduce the amount of trace data we don't care about
38 rubybin = "miniruby#{RbConfig::CONFIG["EXEEXT"]}"
39 rubybin = File.join(File.dirname(EnvUtil.rubybin), rubybin)
40 rubybin = EnvUtil.rubybin unless File.executable?(rubybin)
42 # make sure ruby was built with --enable-dtrace and we can run
43 # dtrace(1) or stap(1):
44 cmd = "#{rubybin} --disable=gems -eexit"
46 when :dtrace; cmd = %W(dtrace -l -n ruby$target:::gc-sweep-end -c #{cmd})
47 when :stap; cmd = %W(stap -l process.mark("gc__sweep__end") -c #{cmd})
49 warn "don't know how to check if built with #{impl} support"
53 NEEDED_ENVS = [RbConfig::CONFIG["LIBPATHENV"], "RUBY", "RUBYOPT"].compact
59 NEEDED_ENVS.each {|name| val = ENV[name] and sudocmd << "#{name}=#{val}"}
61 ok = system(*sudocmd, *cmd, err: IO::NULL, out: IO::NULL)
65 class TestCase < Test::Unit::TestCase
66 INCLUDE = File.expand_path('..', File.dirname(__FILE__))
70 # increase bufsize to 8m (default 4m on Solaris)
71 DTRACE_CMD = %w[dtrace -b 8m]
73 READ_PROBES = proc do |cmd|
75 PTY.spawn(*cmd) do |io, _, pid|
76 lines = io.readlines.each {|line| line.sub!(/\r$/, "")}
83 # only handles simple cases, use a Hash for d_program
84 # if there are more complex cases
85 def dtrace2systemtap(d_program)
86 translate = lambda do |str|
87 # dtrace starts args with '0', systemtap with '1' and prefixes '$'
88 str = str.gsub(/\barg(\d+)/) { "$arg#{$1.to_i + 1}" }
89 # simple function mappings:
90 str.gsub!(/\bcopyinstr\b/, 'user_string')
91 str.gsub!(/\bstrstr\b/, 'isinstr')
96 d_program.split(/^/).each do |l|
98 when /\bruby\$target:::([a-z-]+)/
99 name = $1.gsub(/-/, '__')
100 out << %Q{probe process.mark("#{name}")\n}
102 cond = translate.call($1)
105 out << "if (#{cond}) {\n" if cond
110 out << translate.call(l)
116 DTRACE_CMD ||= %w[dtrace]
118 READ_PROBES ||= proc do |cmd|
119 IO.popen(cmd, err: [:child, :out], &:readlines)
122 def trap_probe d_program, ruby_program
123 if Hash === d_program
124 d_program = d_program[IMPL] or
125 omit "#{d_program} not implemented for #{IMPL}"
126 elsif String === d_program && IMPL == :stap
127 d_program = dtrace2systemtap(d_program)
129 d = Tempfile.new(%w'probe .d')
133 rb = Tempfile.new(%w'probed .rb')
134 rb.write ruby_program
139 cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}"
141 cmd = %W(stap #{d_path} -c #{cmd})
143 cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ]
146 NEEDED_ENVS.each do |name|
148 cmd.unshift("#{name}=#{val}")
153 probes = READ_PROBES.(cmd)
156 yield(d_path, rb_path, probes)
162 DTrace::TestCase.class_variable_set(:@@sudo, sudo)
163 DTrace::TestCase.const_set(:IMPL, impl)
164 DTrace::TestCase.const_set(:RUBYBIN, rubybin)