From 65a227beda8c32972cb6fdb17ca93a4f5e828044 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 25 Jul 2014 12:02:09 -0500 Subject: Correct tools/profile usage example [ci skip] --- tools/profile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/profile') diff --git a/tools/profile b/tools/profile index fbea67492b..d4b2fc5fbd 100755 --- a/tools/profile +++ b/tools/profile @@ -1,6 +1,6 @@ #!/usr/bin/env ruby # Example: -# tools/profile activesupport/lib/active_support.rb +# ./profile ../activesupport/lib/active_support.rb [ruby-prof mode] ENV['NO_RELOAD'] ||= '1' ENV['RAILS_ENV'] ||= 'development' -- cgit v1.2.3 From f36c46754fa420343e7b01695f09e62293a40597 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 25 Jul 2014 12:09:49 -0500 Subject: Encapsulate tools/profile functionality in CodeTools::Profiler Fix bug in path expansion of input filepath [ci skip] --- tools/profile | 128 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 52 deletions(-) (limited to 'tools/profile') diff --git a/tools/profile b/tools/profile index d4b2fc5fbd..5c916aafc8 100755 --- a/tools/profile +++ b/tools/profile @@ -1,71 +1,95 @@ #!/usr/bin/env ruby # Example: -# ./profile ../activesupport/lib/active_support.rb [ruby-prof mode] +# tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer] ENV['NO_RELOAD'] ||= '1' ENV['RAILS_ENV'] ||= 'development' -require 'benchmark' +module CodeTools + class Profiler -module RequireProfiler - private - def require(file, *args) RequireProfiler.profile(file) { super } end - def load(file, *args) RequireProfiler.profile(file) { super } end - - @depth, @stats = 0, [] - class << self - attr_accessor :depth - attr_accessor :stats - - def profile(file) - stats << [file, depth] - self.depth += 1 - result = nil - elapsed = Benchmark.realtime { result = yield } - self.depth -= 1 - stats.pop if stats.last.first == file - stats << [file, depth, elapsed] if result - result + attr_reader :path, :mode + def initialize(path, mode=nil) + @path, @mode = path, mode + require 'benchmark' end - end -end -GC.start -before_rss = `ps -o rss= -p #{Process.pid}`.to_i + def profile_requires + GC.start + before_rss = `ps -o rss= -p #{Process.pid}`.to_i -path = ARGV.shift -if mode = ARGV.shift - require 'ruby-prof' - RubyProf.measure_mode = RubyProf.const_get(mode.upcase) - RubyProf.start -else - Object.instance_eval { include RequireProfiler } -end + if mode + require 'ruby-prof' + RubyProf.measure_mode = RubyProf.const_get(mode.upcase) + RubyProf.start + else + Object.instance_eval { include RequireProfiler } + end + + elapsed = Benchmark.realtime { require path } + results = RubyProf.stop if mode -elapsed = Benchmark.realtime { require path } -results = RubyProf.stop if mode + GC.start + after_rss = `ps -o rss= -p #{Process.pid}`.to_i -GC.start -after_rss = `ps -o rss= -p #{Process.pid}`.to_i + if mode + if printer = ARGV.shift + puts "RubyProf outputting to stderr with printer #{printer}" + RubyProf.const_get("#{printer.to_s.classify}Printer").new(results).print($stdout) + elsif RubyProf.const_defined?(:CallStackPrinter) + filename = "#{File.basename(path, '.rb')}.#{mode}.html" + puts "RubyProf outputting to #{filename}" + File.open(filename, 'w') do |out| + RubyProf::CallStackPrinter.new(results).print(out) + end + else + filename = "#{File.basename(path, '.rb')}.#{mode}.callgrind" + puts "RubyProf outputting to #{filename}" + File.open(filename, 'w') do |out| + RubyProf::CallTreePrinter.new(results).print(out) + end + end + end -if mode - if printer = ARGV.shift - RubyProf.const_get("#{printer.to_s.classify}Printer").new(results).print($stdout) - elsif RubyProf.const_defined?(:CallStackPrinter) - File.open("#{File.basename(path, '.rb')}.#{mode}.html", 'w') do |out| - RubyProf::CallStackPrinter.new(results).print(out) + RequireProfiler.stats.each do |file, depth, sec| + if sec + puts "%8.1f ms %s%s" % [sec * 1000, ' ' * depth, file] + else + puts "#{' ' * (13 + depth)}#{file}" + end + end + puts "%8.1f ms %d KB RSS" % [elapsed * 1000, after_rss - before_rss] end - else - File.open("#{File.basename(path, '.rb')}.#{mode}.callgrind", 'w') do |out| - RubyProf::CallTreePrinter.new(results).print(out) + + module RequireProfiler + private + def require(file, *args) RequireProfiler.profile(file) { super } end + def load(file, *args) RequireProfiler.profile(file) { super } end + + @depth, @stats = 0, [] + class << self + attr_accessor :depth + attr_accessor :stats + + def profile(file) + stats << [file, depth] + self.depth += 1 + result = nil + elapsed = Benchmark.realtime { result = yield } + self.depth -= 1 + stats.pop if stats.last.first == file + stats << [file, depth, elapsed] if result + result + end + end end end end - -RequireProfiler.stats.each do |file, depth, sec| - if sec - puts "%8.1f ms %s%s" % [sec * 1000, ' ' * depth, file] +if $0 == __FILE__ + if (filename = ARGV.shift) + path = File.expand_path(filename) + mode = ARGV.shift + CodeTools::Profiler.new(path, mode).profile_requires else - puts "#{' ' * (13 + depth)}#{file}" + STDERR.puts "No file path entered. Usage is tools/profile path/to/file.rb [ruby-prof mode] [ruby-prof printer]" end end -puts "%8.1f ms %d KB RSS" % [elapsed * 1000, after_rss - before_rss] -- cgit v1.2.3 From 54a4065074485ece05c4fe598d16d108cb292d88 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 25 Jul 2014 13:06:22 -0500 Subject: Optionally add String extensions for ruby-prof printer option [ci skip] --- tools/profile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'tools/profile') diff --git a/tools/profile b/tools/profile index 5c916aafc8..62daa2f0bd 100755 --- a/tools/profile +++ b/tools/profile @@ -84,6 +84,32 @@ module CodeTools end end end +# ruby-prof printer name causes the third arg to be sent :classify +# which is probably overkill if you already know the name of the ruby-prof +# printer you want to use, e.g. Graph +begin + require 'active_support/inflector' + require 'active_support/core_ext/string/inflections' +rescue LoadError + STDERR.puts $!.message + class String + # File activesupport/lib/active_support/inflector/methods.rb, line 150 + def classify + # strip out any leading schema name + camelize(self.sub(/.*\./, '')) + end + # File activesupport/lib/active_support/inflector/methods.rb, line 68 + def camelize(uppercase_first_letter = true) + string = self + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { $&.capitalize } + else + string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase } + end + string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::') + end + end +end if $0 == __FILE__ if (filename = ARGV.shift) path = File.expand_path(filename) -- cgit v1.2.3 From 3cbeb8d8eaf217f8eb38f5ea26f766f5cd978ff3 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 25 Jul 2014 13:23:54 -0500 Subject: Fail profiler fast when input is not a ruby file [ci skip] --- tools/profile | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'tools/profile') diff --git a/tools/profile b/tools/profile index 62daa2f0bd..a35dd18b77 100755 --- a/tools/profile +++ b/tools/profile @@ -6,9 +6,11 @@ ENV['RAILS_ENV'] ||= 'development' module CodeTools class Profiler + Error = Class.new(StandardError) attr_reader :path, :mode def initialize(path, mode=nil) + assert_ruby_file_exists(path) @path, @mode = path, mode require 'benchmark' end @@ -60,6 +62,16 @@ module CodeTools puts "%8.1f ms %d KB RSS" % [elapsed * 1000, after_rss - before_rss] end + private + + def assert_ruby_file_exists(path) + fail Error.new("No such file") unless File.exists?(path) + fail Error.new("#{path} is a directory") if File.directory?(path) + ruby_extension = File.extname(path) == '.rb' + ruby_executable = File.open(path, 'rb') {|f| f.readline } =~ [/\A#!.*ruby/] + fail Error.new("Not a ruby file") unless ruby_extension or ruby_executable + end + module RequireProfiler private def require(file, *args) RequireProfiler.profile(file) { super } end -- cgit v1.2.3