aboutsummaryrefslogtreecommitdiffstats
path: root/tools/profile
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profile')
-rwxr-xr-xtools/profile138
1 files changed, 138 insertions, 0 deletions
diff --git a/tools/profile b/tools/profile
new file mode 100755
index 0000000000..6fb571f43b
--- /dev/null
+++ b/tools/profile
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+# Profile require calls giving information about the time and the files that are called
+# when loading the provided file.
+#
+# Example:
+# tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer]
+ENV["NO_RELOAD"] ||= "1"
+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
+
+ def profile_requires
+ GC.start
+ before_rss = `ps -o rss= -p #{Process.pid}`.to_i
+
+ 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
+
+ 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
+
+ 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
+
+ private
+
+ def assert_ruby_file_exists(path)
+ fail Error.new("No such file") unless File.exist?(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 || ruby_executable
+ end
+
+ 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
+# 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(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]*/) { |match| match.capitalize }
+ else
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.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)
+ mode = ARGV.shift
+ CodeTools::Profiler.new(path, mode).profile_requires
+ else
+ STDERR.puts "No file path entered. Usage is tools/profile path/to/file.rb [ruby-prof mode] [ruby-prof printer]"
+ end
+end