diff --git a/tools/README.md b/tools/README.md index 2dbe65d14a..14a0594cd5 100644 --- a/tools/README.md +++ b/tools/README.md @@ -4,7 +4,6 @@ This is a collection of utilities used for Rails internal development. They aren't used by Rails apps directly. * `console` drops you in irb and loads local Rails repos - * `profile` profiles `Kernel#require` to help reduce startup time * `railspect` provides commands to run internal linters * `line_statistics` provides CodeTools module and LineStatistics class to count lines * `test` is loaded by every major component of Rails to simplify testing, for example: diff --git a/tools/profile b/tools/profile deleted file mode 100755 index d38ba1dd84..0000000000 --- a/tools/profile +++ /dev/null @@ -1,137 +0,0 @@ -#!/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