mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			165 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # -*- coding: us-ascii -*-
 | |
| # frozen_string_literal: false
 | |
| require 'test/unit'
 | |
| require 'tempfile'
 | |
| 
 | |
| if Process.euid == 0
 | |
|   ok = true
 | |
| elsif (sudo = ENV["SUDO"]) and !sudo.empty? and (`#{sudo} echo ok` rescue false)
 | |
|   ok = true
 | |
| 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
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| # 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
 | |
|       READ_PROBES = proc do |cmd|
 | |
|         lines = nil
 | |
|         PTY.spawn(*cmd) do |io, _, pid|
 | |
|           lines = io.readlines.each {|line| line.sub!(/\r$/, "")}
 | |
|           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]
 | |
| 
 | |
|     READ_PROBES ||= proc do |cmd|
 | |
|       IO.popen(cmd, err: [:child, :out], &:readlines)
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|       rb = Tempfile.new(%w'probed .rb')
 | |
|       rb.write ruby_program
 | |
|       rb.flush
 | |
| 
 | |
|       d_path  = d.path
 | |
|       rb_path = 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
 | |
|         NEEDED_ENVS.each do |name|
 | |
|           if val = ENV[name]
 | |
|             cmd.unshift("#{name}=#{val}")
 | |
|           end
 | |
|         end
 | |
|         cmd.unshift(sudo)
 | |
|       end
 | |
|       probes = READ_PROBES.(cmd)
 | |
|       d.close(true)
 | |
|       rb.close(true)
 | |
|       yield(d_path, rb_path, probes)
 | |
|     end
 | |
|   end
 | |
| 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
 | 
