bin/ruby-prof in ruby-prof-1.1.0 vs bin/ruby-prof in ruby-prof-1.2.0

- old
+ new

@@ -1,9 +1,8 @@ #! /usr/bin/env ruby # First require ruby-prof -require 'rubygems' require 'ruby-prof' # Now setup option parser require 'ostruct' require 'optparse' @@ -12,68 +11,49 @@ # == Synopsis # # Profiles a Ruby program. # # == Usage + # ruby-prof [options] <script.rb> [--] [profiled-script-command-line-options] # - # ruby_prof [options] <script.rb> [--] [script-options] - # # Options: - # -p, --printer=printer Select a printer: - # flat - Prints a flat profile as text (default). - # graph - Prints a graph profile as text. - # graph_html - Prints a graph profile as html. - # call_tree - format for KCacheGrind - # call_stack - prints a HTML visualization of the call tree - # dot - Prints a graph profile as a dot file - # multi - Creates several reports in output directory - # - # -m, --min_percent=min_percent The minimum percent a method must take before - # being included in output reports. This option is not supported for call tree reports. - # - # -f, --file=path Output results to a file instead of standard out. - # - # --mode=measure_mode Select what ruby-prof should measure: - # wall - Wall time (default) - # process - Process time - # allocations - Object allocations - # memory - Allocated memory - # - # -s, --sort=sort_mode Select how ruby-prof results should be sorted: - # total - Total time - # self - Self time - # wait - Wait time - # child - Child time - # - # --replace-progname Replace $0 when loading the .rb files. - # - # --specialized-instruction Turn on specified instruction. - # - # -v Show version, set $VERBOSE to true, profile script if option given - # - # -d Set $DEBUG to true - # - # -R, --require-noprof lib Require a specific library (not profiled) - # - # -E, --eval-noprof code Execute the ruby statements (not profiled) - # - # -x, --exclude regexp Exclude methods by regexp (see method elimination) - # - # -X, --exclude-file file Exclude methods by regexp listed in file (see method elimination) - # - # --exclude-common-cycles Make common iterators like Integer#times appear inlined - # - # --exclude-common-callbacks Make common callbacks invocations like Integer#times appear inlined so you can see call origins in graph - # - # -h, --help Show help message - # - # --version Show version - # - # + # -p, --printer=printer Select a printer: + # flat - Prints a flat profile as text (default). + # graph - Prints a graph profile as text. + # graph_html - Prints a graph profile as html. + # call_tree - format for KCacheGrind + # call_stack - prints a HTML visualization of the call tree + # dot - Prints a graph profile as a dot file + # multi - Creates several reports in output directory + # -m, --min_percent=min_percent The minimum percent a method must take before + # being included in output reports. + # This option is not supported for call tree. + # -f, --file=path Output results to a file instead of standard out. + # --mode=measure_mode Select what ruby-prof should measure: + # wall - Wall time (default). + # process - Process time. + # allocations - Object allocations (requires patched Ruby interpreter). + # memory - Allocated memory in KB (requires patched Ruby interpreter). + # -s, --sort=sort_mode Select how ruby-prof results should be sorted: + # total - Total time + # self - Self time + # wait - Wait time + # child - Child time + # --allow_exceptions Raise exceptions encountered during profiling (true) or suppress them (false) + # -R, --require-noprof=lib require a specific library (not profiled) + # -E, --eval-noprof=code execute the ruby statements (not profiled) + # --exclude=methods A comma separated list of methods to exclude. + # Specify instance methods via # (Integer#times) + # Specify class methods via . (Integer.superclass) + # --exclude-common Remove common methods from the profile + # -h, --help Show help message + # -v, --version version Show version (1.1.0) + class Cmd # :enddoc: attr_accessor :options + attr_reader :profile def initialize setup_options parse_args @@ -82,19 +62,56 @@ end def setup_options @options = OpenStruct.new options.printer = RubyProf::FlatPrinter + options.measure_mode = RubyProf::WALL_TIME options.min_percent = 0 options.file = nil - options.replace_prog_name = false - options.specialized_instruction = false - + options.allow_exceptions = false + options.exclude_common = false + options.exclude = Array.new options.pre_libs = Array.new options.pre_execs = Array.new end + # This is copied from ActiveSupport: + def constantize(camel_cased_word) + if !camel_cased_word.include?("::") + Object.const_get(camel_cased_word) + else + names = camel_cased_word.split("::") + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + end + def option_parser OptionParser.new do |opts| opts.banner = "ruby_prof #{RubyProf::VERSION}\n" + "Usage: ruby-prof [options] <script.rb> [--] [profiled-script-command-line-options]" @@ -180,99 +197,50 @@ when :child :children_time end end - opts.on("--replace-progname", "Replace $0 when loading the .rb files.") do - options.replace_prog_name = true - end - - if defined?(RubyVM) - opts.on("--specialized-instruction", "Turn on specified instruction.") do - options.specialized_instruction = true - end - end - opts.on_tail("-h", "--help", "Show help message") do puts opts exit end - opts.on_tail("--version", "Show version #{RubyProf::VERSION}") do + opts.on_tail("-v version", "--version", "Show version (#{RubyProf::VERSION})") do puts "ruby_prof " + RubyProf::VERSION exit end - opts.on("-v","Show version, set $VERBOSE to true, profile script if option given") do - puts "ruby version: " + [RUBY_PATCHLEVEL, RUBY_PLATFORM, RUBY_VERSION].join(' ') - $VERBOSE = true + opts.on('--allow_exceptions', 'Raise exceptions encountered during profiling (true) or suppress them (false)') do + options.allow_exceptions = true end - opts.on("-d", "Set $DEBUG to true") do - $DEBUG = true - end - - opts.on('-R lib', '--require-noprof lib', 'require a specific library (not profiled)') do |lib| + opts.on('-R lib', '--require-noprof=lib', 'require a specific library (not profiled)') do |lib| options.pre_libs << lib end - opts.on('-E code', '--eval-noprof code', 'execute the ruby statements (not profiled)') do |code| + opts.on('-E code', '--eval-noprof=code', 'execute the ruby statements (not profiled)') do |code| options.pre_execs << code end - opts.on('-x regexp', '--exclude regexp', 'exclude methods by regexp (see method elimination)') do |meth| - options.eliminate_methods ||= [] - options.eliminate_methods << Regexp.new(meth) + opts.on('--exclude=methods', String, + 'A comma separated list of methods to exclude.', + ' Specify instance methods via # (Integer#times)', + ' Specify class methods via . (Integer.superclass)') do |exclude_string| + exclude_string.split(',').each do |string| + match = string.strip.match(/(.*)(#|\.)(.*)/) + klass = constantize(match[1]) + if match[2] == '.' + klass = klass.singleton_class + end + method = match[3].to_sym + options.exclude << [klass, method] + end end - opts.on('-X file', '--exclude-file file', 'exclude methods by regexp listed in file (see method elimination)') do|file| - options.eliminate_methods_files ||= [] - options.eliminate_methods_files << file + opts.on('--exclude-common', 'Remove common methods from the profile') do + options.exclude_common = true end - - opts.on('--exclude-common-cycles', 'make common iterators like Integer#times appear inlined') do |meth| - options.eliminate_methods ||= [] - options.eliminate_methods += %w{ - Integer#times - Integer#upto - Integer#downto - Enumerator#each - Enumerator#each_with_index - Enumerator#each_with_object - - Array#each - Array#each_index - Array#reverse_each - Array#map - - Hash#each - Hash#each_pair - Hash#each_key - Hash#each_value - - Range#each - Enumerable#each_cons - Enumerable#each_entry - Enumerable#each_slice - Enumerable#each_with_index - Enumerable#each_with_object - Enumerable#reverse_each - Enumerable#inject - Enumerable#collect - Enumerable#reduce - } - #TODO: may be the whole Enumerable module should be excluded via 'Enumerable#.*', we need feedback on use cases. - end - - opts.on('--exclude-common-callbacks', 'make common callbacks invocations like Integer#times appear inlined so you can see call origins in graph') do|meth| - options.eliminate_methods ||= [] - options.eliminate_methods += %w{ - Method#call - Proc#call - ActiveSupport::Callbacks::ClassMethods#__run_callback - } - end end end def parse_args # Make sure the user specified at least one file @@ -311,27 +279,19 @@ eval(exec) end end def run - # Get the script we will execute script = ARGV.shift - if options.replace_prog_name - $0 = File.expand_path(script) + @profile = Profile.new(options.to_h) + options.exclude.each do |klass, method| + @profile.exclude_method!(klass, method) end - # Set VM compile option - if defined?(RubyVM) - RubyVM::InstructionSequence.compile_option = { - :trace_instruction => true, - :specialized_instruction => options.specialized_instruction - } + profile.profile do + load script end - - # Set the measure mode - RubyProf.measure_mode = options.measure_mode if options.measure_mode - RubyProf.start_script(script) end end end # Parse command line options @@ -340,23 +300,11 @@ # Install at_exit handler. It is important that we do this # before loading the scripts so our at_exit handler run # *after* any other one that will be installed. at_exit { - # Stop profiling - result = RubyProf.stop - - # Eliminate unwanted methods from call graph - if cmd.options.eliminate_methods - result.eliminate_methods!(cmd.options.eliminate_methods) - end - - if cmd.options.eliminate_methods_files - cmd.options.eliminate_methods_files.each {|f| result.eliminate_methods!(f)} - end - # Create a printer - printer = cmd.options.printer.new(result) + printer = cmd.options.printer.new(cmd.profile) printer_options = {:min_percent => cmd.options.min_percent, :sort_method => cmd.options.sort_method} # Get output if cmd.options.file # write it relative to the dir they *started* in, as it's a bit surprising to write it in the dir they end up in.