├── Rakefile ├── lib ├── profile │ └── version.rb ├── profile.rb └── profiler.rb ├── Gemfile ├── .gitignore ├── bin ├── setup └── console ├── profile.gemspec └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /lib/profile/version.rb: -------------------------------------------------------------------------------- 1 | module Profile 2 | VERSION = "0.4.0" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "bundler" 6 | gem "rake" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/profile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'profiler' 3 | 4 | RubyVM::InstructionSequence.compile_option = { 5 | :trace_instruction => true, 6 | :specialized_instruction => false 7 | } 8 | END { 9 | Profiler__::print_profile(STDERR) 10 | } 11 | Profiler__::start_profile 12 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "profile" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /profile.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib", __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "profile/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "profile" 7 | spec.version = Profile::VERSION 8 | spec.authors = ["Yukihiro Matsumoto"] 9 | spec.email = ["matz@ruby-lang.org"] 10 | 11 | spec.summary = %q{Profile provides a way to Profile your Ruby application.} 12 | spec.description = %q{Profile provides a way to Profile your Ruby application.} 13 | spec.homepage = "https://github.com/ruby/profile" 14 | 15 | spec.metadata["homepage_uri"] = spec.homepage 16 | spec.metadata["source_code_uri"] = "https://github.com/ruby/profile.git" 17 | 18 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 19 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 20 | end 21 | spec.bindir = "exe" 22 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 23 | spec.require_paths = ["lib"] 24 | end 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Profile 2 | 3 | Profile provides a way to Profile your Ruby application. 4 | 5 | Profiling your program is a way of determining which methods are called and 6 | how long each method takes to complete. This way you can detect which 7 | methods are possible bottlenecks. 8 | 9 | Profiling your program will slow down your execution time considerably, 10 | so activate it only when you need it. Don't confuse benchmarking with 11 | profiling. 12 | 13 | ## Installation 14 | 15 | Add this line to your application's Gemfile: 16 | 17 | ```ruby 18 | gem 'profile' 19 | ``` 20 | 21 | And then execute: 22 | 23 | $ bundle 24 | 25 | Or install it yourself as: 26 | 27 | $ gem install profile 28 | 29 | ## Usage 30 | 31 | There are two ways to activate Profiling: 32 | 33 | ### Command line 34 | 35 | Run your Ruby script with -rprofile: 36 | 37 | ```bash 38 | $ ruby -rprofile example.rb 39 | ``` 40 | 41 | If you're profiling an executable in your $PATH you can use 42 | ruby -S: 43 | 44 | ```bash 45 | $ ruby -rprofile -S some_executable 46 | ``` 47 | 48 | ### From code 49 | 50 | Just require 'profile': 51 | 52 | ```ruby 53 | require 'profile' 54 | 55 | def slow_method 56 | 5000.times do 57 | 9999999999999999*999999999 58 | end 59 | end 60 | 61 | def fast_method 62 | 5000.times do 63 | 9999999999999999+999999999 64 | end 65 | end 66 | 67 | slow_method 68 | fast_method 69 | ``` 70 | 71 | The output in both cases is a report when the execution is over: 72 | 73 | ``` 74 | $ ruby -rprofile example.rb 75 | 76 | % cumulative self self total 77 | time seconds seconds calls ms/call ms/call name 78 | 68.42 0.13 0.13 2 65.00 95.00 Integer#times 79 | 15.79 0.16 0.03 5000 0.01 0.01 Fixnum#* 80 | 15.79 0.19 0.03 5000 0.01 0.01 Fixnum#+ 81 | 0.00 0.19 0.00 2 0.00 0.00 IO#set_encoding 82 | 0.00 0.19 0.00 1 0.00 100.00 Object#slow_method 83 | 0.00 0.19 0.00 2 0.00 0.00 Module#method_added 84 | 0.00 0.19 0.00 1 0.00 90.00 Object#fast_method 85 | 0.00 0.19 0.00 1 0.00 190.00 #toplevel 86 | ``` 87 | 88 | ## Development 89 | 90 | After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 91 | 92 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 93 | 94 | ## Contributing 95 | 96 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/profile. 97 | -------------------------------------------------------------------------------- /lib/profiler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Profile provides a way to Profile your Ruby application. 3 | # 4 | # Profiling your program is a way of determining which methods are called and 5 | # how long each method takes to complete. This way you can detect which 6 | # methods are possible bottlenecks. 7 | # 8 | # Profiling your program will slow down your execution time considerably, 9 | # so activate it only when you need it. Don't confuse benchmarking with 10 | # profiling. 11 | # 12 | # There are two ways to activate Profiling: 13 | # 14 | # == Command line 15 | # 16 | # Run your Ruby script with -rprofile: 17 | # 18 | # ruby -rprofile example.rb 19 | # 20 | # If you're profiling an executable in your $PATH you can use 21 | # ruby -S: 22 | # 23 | # ruby -rprofile -S some_executable 24 | # 25 | # == From code 26 | # 27 | # Just require 'profile': 28 | # 29 | # require 'profile' 30 | # 31 | # def slow_method 32 | # 5000.times do 33 | # 9999999999999999*999999999 34 | # end 35 | # end 36 | # 37 | # def fast_method 38 | # 5000.times do 39 | # 9999999999999999+999999999 40 | # end 41 | # end 42 | # 43 | # slow_method 44 | # fast_method 45 | # 46 | # The output in both cases is a report when the execution is over: 47 | # 48 | # ruby -rprofile example.rb 49 | # 50 | # % cumulative self self total 51 | # time seconds seconds calls ms/call ms/call name 52 | # 68.42 0.13 0.13 2 65.00 95.00 Integer#times 53 | # 15.79 0.16 0.03 5000 0.01 0.01 Fixnum#* 54 | # 15.79 0.19 0.03 5000 0.01 0.01 Fixnum#+ 55 | # 0.00 0.19 0.00 2 0.00 0.00 IO#set_encoding 56 | # 0.00 0.19 0.00 1 0.00 100.00 Object#slow_method 57 | # 0.00 0.19 0.00 2 0.00 0.00 Module#method_added 58 | # 0.00 0.19 0.00 1 0.00 90.00 Object#fast_method 59 | # 0.00 0.19 0.00 1 0.00 190.00 #toplevel 60 | 61 | module Profiler__ 62 | class Wrapper < Struct.new(:defined_class, :method_id, :hash) # :nodoc: 63 | private :defined_class=, :method_id=, :hash= 64 | 65 | def initialize(klass, mid) 66 | super(klass, mid, nil) 67 | self.hash = Struct.instance_method(:hash).bind(self).call 68 | end 69 | 70 | def to_s 71 | "#{defined_class.inspect}#".sub(/\A\##\z/, '\1.') << method_id.to_s 72 | end 73 | alias inspect to_s 74 | end 75 | 76 | # internal values 77 | @@start = nil # the start time that profiling began 78 | @@stacks = nil # the map of stacks keyed by thread 79 | @@maps = nil # the map of call data keyed by thread, class and id. Call data contains the call count, total time, 80 | PROFILE_CALL_PROC = TracePoint.new(*%i[call c_call b_call]) {|tp| # :nodoc: 81 | now = Process.times[0] 82 | stack = (@@stacks[Thread.current] ||= []) 83 | stack.push [now, 0.0] 84 | } 85 | PROFILE_RETURN_PROC = TracePoint.new(*%i[return c_return b_return]) {|tp| # :nodoc: 86 | now = Process.times[0] 87 | key = Wrapper.new(tp.defined_class, tp.method_id) 88 | stack = (@@stacks[Thread.current] ||= []) 89 | if tick = stack.pop 90 | threadmap = (@@maps[Thread.current] ||= {}) 91 | data = (threadmap[key] ||= [0, 0.0, 0.0, key]) 92 | data[0] += 1 93 | cost = now - tick[0] 94 | data[1] += cost 95 | data[2] += cost - tick[1] 96 | stack[-1][1] += cost if stack[-1] 97 | end 98 | } 99 | module_function 100 | # Starts the profiler. 101 | # 102 | # See Profiler__ for more information. 103 | def start_profile 104 | @@start = Process.times[0] 105 | @@stacks = {} 106 | @@maps = {} 107 | PROFILE_CALL_PROC.enable 108 | PROFILE_RETURN_PROC.enable 109 | end 110 | # Stops the profiler. 111 | # 112 | # See Profiler__ for more information. 113 | def stop_profile 114 | PROFILE_CALL_PROC.disable 115 | PROFILE_RETURN_PROC.disable 116 | end 117 | # Outputs the results from the profiler. 118 | # 119 | # See Profiler__ for more information. 120 | def print_profile(f) 121 | stop_profile 122 | total = Process.times[0] - @@start 123 | if total == 0 then total = 0.01 end 124 | totals = {} 125 | @@maps.values.each do |threadmap| 126 | threadmap.each do |key, data| 127 | total_data = (totals[key] ||= [0, 0.0, 0.0, key]) 128 | total_data[0] += data[0] 129 | total_data[1] += data[1] 130 | total_data[2] += data[2] 131 | end 132 | end 133 | 134 | # Maybe we should show a per thread output and a totals view? 135 | 136 | data = totals.values 137 | data = data.sort_by{|x| -x[2]} 138 | sum = 0 139 | f.printf " %% cumulative self self total\n" 140 | f.printf " time seconds seconds calls ms/call ms/call name\n" 141 | for d in data 142 | sum += d[2] 143 | f.printf "%6.2f %8.2f %8.2f %8d ", d[2]/total*100, sum, d[2], d[0] 144 | f.printf "%8.2f %8.2f %s\n", d[2]*1000/d[0], d[1]*1000/d[0], d[3] 145 | end 146 | f.printf "%6.2f %8.2f %8.2f %8d ", 0.0, total, 0.0, 1 # ??? 147 | f.printf "%8.2f %8.2f %s\n", 0.0, total*1000, "#toplevel" # ??? 148 | end 149 | end 150 | --------------------------------------------------------------------------------