├── 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 |
--------------------------------------------------------------------------------