railsprof 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +83 -0
- data/Rakefile +1 -0
- data/bin/railsprof +5 -0
- data/lib/railsprof/cli.rb +137 -0
- data/lib/railsprof/lineprof_parser.rb +78 -0
- data/lib/railsprof/logger.rb +22 -0
- data/lib/railsprof/paths.rb +30 -0
- data/lib/railsprof/profiler.rb +230 -0
- data/lib/railsprof/version.rb +3 -0
- data/lib/railsprof/views/railsprof-tmpl.html.erb +61 -0
- data/lib/railsprof.rb +17 -0
- data/railsprof.gemspec +27 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd676c3941cfebbd315805ed26916b228d3851e2
|
4
|
+
data.tar.gz: 0e3a3adfd11b13be3370e992d4b3f97e6713527a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 94968394aacd9891748bc669235f6fea34760e08aef2fe7a56b02e3ed2f2c02a5b3085d95b13342c809c5af81dfb96bd94cd69d8408c15b7f1f3acd9c8c402ad
|
7
|
+
data.tar.gz: c6bb5b06f0b9928194699ef571c7dca8e8a22a8da3fe9b336465deee9e49bc40f3f5ab66749583fc73afcfd02ea75d049fec507022faaee0779871bb2e4b5d6f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Clifton King
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Railsprof
|
2
|
+
|
3
|
+
Rails CLI utility for profiling via rblineprof with good HTML output.
|
4
|
+
|
5
|
+
![image](https://f.cloud.github.com/assets/14217/2202180/a8cd5ab4-98fc-11e3-8c1a-3ca127f26ae2.png)
|
6
|
+
|
7
|
+
```
|
8
|
+
$ be railsprof -n5 -g hasherdashery -q key=REDACTED /api/v3/communities/123/portals
|
9
|
+
info App loading...
|
10
|
+
info App loaded in 12.02 secs
|
11
|
+
info Warmup #1 completed in 1133.25ms
|
12
|
+
info Profiling....
|
13
|
+
info Profile #1 completed in 467.55ms
|
14
|
+
info Profile #2 completed in 463.84ms
|
15
|
+
info Profile #3 completed in 447.02ms
|
16
|
+
info Profile #4 completed in 458.12ms
|
17
|
+
info Profile #5 completed in 448.42ms
|
18
|
+
info All profiles completed in 2287.82ms
|
19
|
+
|
20
|
+
-- Top files by execution time (total / child / excl / filename) --
|
21
|
+
2230.80ms 2229.39ms 1.39ms app/controllers/api/v3/base_controller.rb
|
22
|
+
2163.61ms 4285.14ms 0.59ms app/controllers/api/v3/communities_controller.rb
|
23
|
+
2162.76ms 4174.57ms 110.10ms lib/api/interaction_responder.rb
|
24
|
+
1988.38ms 1986.27ms 2.11ms lib/api/utilities.rb
|
25
|
+
1986.27ms 3698.37ms 920.62ms hasherdashery-200a6c732704/lib/hasherdashery.rb
|
26
|
+
1917.27ms 3330.16ms 545.64ms hasherdashery-200a6c732704/lib/hasherdashery/tailor.rb
|
27
|
+
1668.67ms 2632.86ms 888.21ms hasherdashery-200a6c732704/lib/hasherdashery/property.rb
|
28
|
+
1629.22ms 2307.68ms 1155.85ms hasherdashery-200a6c732704/lib/hasherdashery/value.rb
|
29
|
+
1461.89ms 1849.78ms 1440.31ms hasherdashery-200a6c732704/lib/hasherdashery/data_type/base.rb
|
30
|
+
1416.28ms 1838.26ms 301.43ms hasherdashery-200a6c732704/lib/hasherdashery/data_type/pattern.rb
|
31
|
+
594.30ms 492.44ms 101.86ms hasherdashery-200a6c732704/lib/hasherdashery/dsl/pattern_maker.rb
|
32
|
+
457.08ms 406.78ms 50.31ms lib/patterns/api/v3/common_patterns.rb
|
33
|
+
424.95ms 530.18ms 402.26ms hasherdashery-200a6c732704/lib/hasherdashery/dsl/common_methods.rb
|
34
|
+
311.47ms 0.00ms 311.47ms lib/api/router.rb
|
35
|
+
274.65ms 329.48ms 55.15ms lib/patterns/api/v3/portal_patterns.rb
|
36
|
+
247.06ms 121.02ms 126.04ms hasherdashery-200a6c732704/lib/hasherdashery/dsl/simple_type_methods.rb
|
37
|
+
229.35ms 142.72ms 86.63ms hasherdashery-200a6c732704/lib/hasherdashery/pattern_rack.rb
|
38
|
+
176.25ms 182.78ms 100.57ms hasherdashery-200a6c732704/lib/hasherdashery/dsl/pin_method.rb
|
39
|
+
161.32ms 20.78ms 140.54ms hasherdashery-200a6c732704/lib/hasherdashery/type_label.rb
|
40
|
+
127.24ms 4.97ms 122.27ms hasherdashery-200a6c732704/lib/hasherdashery/type_universe.rb
|
41
|
+
66.29ms 0.00ms 66.29ms hasherdashery-200a6c732704/lib/hasherdashery/label.rb
|
42
|
+
```
|
43
|
+
|
44
|
+
## Installation
|
45
|
+
|
46
|
+
Add this line to your application's Gemfile:
|
47
|
+
|
48
|
+
gem 'railsprof'
|
49
|
+
|
50
|
+
And then execute:
|
51
|
+
|
52
|
+
$ bundle
|
53
|
+
|
54
|
+
Or install it yourself as:
|
55
|
+
|
56
|
+
$ gem install railsprof
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
|
60
|
+
`bundle exec railsprof` from your Rails root.
|
61
|
+
|
62
|
+
```
|
63
|
+
$ be railsprof
|
64
|
+
Usage: railsprof [options] [method] /path
|
65
|
+
-h, --help Show this message
|
66
|
+
--version Show version
|
67
|
+
-v, --verbose Run verbosely
|
68
|
+
-e, --environment ENV Environment (defaults to RAILS_ENV)
|
69
|
+
-q, --query-param KEY=VAL Add query paramter (-q key=val {key: "val"}
|
70
|
+
-w, --warmups N Number of warmup runs on stack, default 1
|
71
|
+
-n, --num-runs N Number of runs in profiling mode, default 1
|
72
|
+
-t, --threshold N Threshold for file output in millis, default: 0.5
|
73
|
+
-d, --directory DIR Local paths to profile, default: app, lib, config
|
74
|
+
-g, --gem GEM Gems to profile, default:
|
75
|
+
```
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
|
79
|
+
1. Fork it ( http://github.com/orgsync/railsprof/fork )
|
80
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
81
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
82
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
83
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/railsprof
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class Railsprof::CLI
|
4
|
+
def self.start
|
5
|
+
logger = Railsprof::Logger.new(STDOUT)
|
6
|
+
logger.level = Logger::INFO
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
opt_parser = OptionParser.new do |opts|
|
10
|
+
opts.banner = 'Usage: railsprof [options] /path'
|
11
|
+
|
12
|
+
opts.on('-h', '--help', 'Show this message') do
|
13
|
+
puts opts.help
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on('--version', 'Show version') do
|
18
|
+
puts "Version #{Railsprof::VERSION}"
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-v', '--verbose',
|
23
|
+
'Run verbosely') do
|
24
|
+
logger.level = options[:log_level] = Logger::DEBUG
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-e', '--environment ENV',
|
28
|
+
'Environment (defaults to RAILS_ENV)') do |env|
|
29
|
+
ENV['RAILS_ENV'] = env
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-q', '--query-param KEY=VAL',
|
33
|
+
'Add query paramter (-q key=val {key: "val"}') do |q|
|
34
|
+
options[:params] ||= {}
|
35
|
+
key, val = q.split('=', 2)
|
36
|
+
options[:params][key] = val
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO add session support
|
40
|
+
# opts.on('-s', '--session KEY=VAL',
|
41
|
+
# 'Session info (-s user=3 --> {user: 3})') do |s|
|
42
|
+
# options[:session] ||= {}
|
43
|
+
# key, val = s.split('=', 2)
|
44
|
+
|
45
|
+
# # parse session value in ruby if possible
|
46
|
+
# options[:session][key.to_sym] =
|
47
|
+
# begin
|
48
|
+
# eval val
|
49
|
+
# rescue NameError
|
50
|
+
# val
|
51
|
+
# end
|
52
|
+
|
53
|
+
# logger.debug "Added to session: {#{key.to_sym.inspect} => #{val.inspect}}"
|
54
|
+
# end
|
55
|
+
|
56
|
+
# TODO add cookie support
|
57
|
+
# opts.on('-c', '--cookie KEY=VAL',
|
58
|
+
# 'Add cookie (-c remember=all --> {:remember => "all"})') do |s|
|
59
|
+
# options[:cookies] ||= {}
|
60
|
+
# key, val = s.split('=', 2)
|
61
|
+
|
62
|
+
# options[:cookies][key.to_sym] = val
|
63
|
+
|
64
|
+
# logger.debug "Added to cookiejar: {#{key.to_sym.inspect} => #{val.inspect}}"
|
65
|
+
# end
|
66
|
+
|
67
|
+
# TODO add host support
|
68
|
+
# opts.on('--host HOST',
|
69
|
+
# 'Host for request (--host www.blah.com)') do |h|
|
70
|
+
# options[:host] = h
|
71
|
+
# end
|
72
|
+
|
73
|
+
# TODO add port support
|
74
|
+
# opts.on('--port PORT',
|
75
|
+
# Integer,
|
76
|
+
# 'Port for request (--port 3000)') do |p|
|
77
|
+
# options[:port] = p
|
78
|
+
# end
|
79
|
+
|
80
|
+
opts.on('-w', '--warmups N', Integer,
|
81
|
+
'Number of warmup runs on stack, default ' +
|
82
|
+
Railsprof::DEFAULT_OPTIONS[:warmups].to_s) do |w|
|
83
|
+
options[:warmups] = w
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on('-n', '--num-runs N', Integer,
|
87
|
+
'Number of runs in profiling mode, default ' +
|
88
|
+
Railsprof::DEFAULT_OPTIONS[:runs].to_s) do |r|
|
89
|
+
options[:runs] = r
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on('-t', '--threshold N', Float,
|
93
|
+
'Threshold for file output in millis, default: ' +
|
94
|
+
Railsprof::DEFAULT_OPTIONS[:threshold].to_s) do |t|
|
95
|
+
options[:threshold] = t
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on('-d', '--directory DIR',
|
99
|
+
'Local paths to profile, default: ' +
|
100
|
+
Railsprof::DEFAULT_OPTIONS[:app_paths].join(', ')) do |d|
|
101
|
+
options[:app_dirs] ||= []
|
102
|
+
options[:app_dirs] << d
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on('-g', '--gem GEM',
|
106
|
+
'Gems to profile, default: ' +
|
107
|
+
Railsprof::DEFAULT_OPTIONS[:gems].join(', ')) do |d|
|
108
|
+
options[:gems] ||= []
|
109
|
+
options[:gems] << d
|
110
|
+
end
|
111
|
+
|
112
|
+
# for capturing args before '--'
|
113
|
+
# opts.on do |h|
|
114
|
+
# puts "head: #{h.inspect}"
|
115
|
+
# end
|
116
|
+
|
117
|
+
# # for capturing args after '--'
|
118
|
+
# opts.on_tail do |t|
|
119
|
+
# puts "tail: #{t.inspect}"
|
120
|
+
# end
|
121
|
+
|
122
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
123
|
+
puts opts
|
124
|
+
exit
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
args = opt_parser.parse!
|
129
|
+
|
130
|
+
if args.empty?
|
131
|
+
puts opt_parser.help
|
132
|
+
exit
|
133
|
+
end
|
134
|
+
|
135
|
+
Railsprof::Profiler.new(args, options).profile!
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class Railsprof::LineprofParser
|
4
|
+
# rblineprof formatting
|
5
|
+
# {
|
6
|
+
# "/path/to/file" => [
|
7
|
+
# # File stats (Line 0)
|
8
|
+
# [total_time, child_time, excl_time, total_cpu, child_cpu, excl_cpu],
|
9
|
+
# # Line 1 stats
|
10
|
+
# [wall, cpu, calls, allocations]
|
11
|
+
# # Line 2 stats
|
12
|
+
# [wall, cpu, calls, allocations]
|
13
|
+
# ...
|
14
|
+
# ]
|
15
|
+
# }
|
16
|
+
|
17
|
+
def initialize(profile, paths, threshold_ms: 0.5)
|
18
|
+
@threshold = threshold_ms * 1000
|
19
|
+
@paths = paths
|
20
|
+
@profile = profile
|
21
|
+
@friendly_paths = {}
|
22
|
+
@profiled_source = {}
|
23
|
+
|
24
|
+
@profiled_files = profile
|
25
|
+
.reduce([]) do |files, (file, lines)|
|
26
|
+
total = lines[0][0]
|
27
|
+
if total > @threshold # && !file[/benchmark|\.rake$/] # time in micros
|
28
|
+
@friendly_paths[file] = @paths.relative_path_for(file)
|
29
|
+
files << [file, *lines[0]]
|
30
|
+
end
|
31
|
+
files
|
32
|
+
end
|
33
|
+
.sort_by { |f| -f[1] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def cli_report
|
37
|
+
puts "\n-- Top files by execution time (total / child / excl / filename) --\n"
|
38
|
+
|
39
|
+
@profiled_files.each do |file, total, child, excl|
|
40
|
+
printf "%9.2fms %9.2fms %9.2fms %s\n" %
|
41
|
+
[total / 1000.0, child / 1000.0, excl / 1000.0, @friendly_paths[file]]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def html_report
|
46
|
+
filename = [
|
47
|
+
'/tmp/railsprof',
|
48
|
+
File.basename(Dir.pwd),
|
49
|
+
Time.now.to_s(:number),
|
50
|
+
].join('-') + '.html'
|
51
|
+
|
52
|
+
@profiled_files.each { |f, _| file_output(f) }
|
53
|
+
|
54
|
+
b = binding
|
55
|
+
template = File.read(File.dirname(__FILE__) +
|
56
|
+
'/views/railsprof-tmpl.html.erb')
|
57
|
+
ERB.new(template, 0, "", "@html_output").result(b)
|
58
|
+
File.open(filename, 'w') { |f| f.write(@html_output) }
|
59
|
+
|
60
|
+
`open #{filename}`
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def file_output(file)
|
66
|
+
@profiled_source[file] = "\n % 8s + % 8s (called)\n" % %w(cpu idle)
|
67
|
+
File.readlines(file).each_with_index do |line, num|
|
68
|
+
wall, cpu, calls, _allocations = @profile[file][num + 1]
|
69
|
+
@profiled_source[file] <<
|
70
|
+
if calls && calls > 0
|
71
|
+
'% 8.1fms + % 8.1fms % 8s | %s' %
|
72
|
+
[cpu / 1000.0, (wall - cpu) / 1000.0, "(#{calls})", line]
|
73
|
+
else
|
74
|
+
' | %s' % line
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Railsprof::Logger < ::Logger
|
4
|
+
SEVERITY_TO_COLOR_MAP = Hash[*%w(
|
5
|
+
DEBUG 0;37
|
6
|
+
INFO 32
|
7
|
+
WARN 33
|
8
|
+
FATAL 31
|
9
|
+
UKNOWN 37
|
10
|
+
)]
|
11
|
+
|
12
|
+
def initialize(*_)
|
13
|
+
super
|
14
|
+
self.formatter = ->(severity, datetime, progname, msg) {
|
15
|
+
[
|
16
|
+
"\033[#{SEVERITY_TO_COLOR_MAP[severity]}m",
|
17
|
+
'%-6s' % "#{severity.downcase}",
|
18
|
+
"\033[0m #{msg.strip}\n"
|
19
|
+
].join
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Railsprof::Paths
|
2
|
+
def initialize(gems: [], app_paths: [])
|
3
|
+
@pwd = Pathname.new(Dir.pwd)
|
4
|
+
@gem_roots = Set.new
|
5
|
+
@paths =
|
6
|
+
app_paths.map { |d| [Dir.pwd, d].join('/') } +
|
7
|
+
gems.map { |g|
|
8
|
+
gem_dir = Gem::Specification.find_by_name(g).gem_dir
|
9
|
+
@gem_roots << Pathname.new(gem_dir).parent
|
10
|
+
gem_dir
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def regexp
|
15
|
+
Regexp.new(
|
16
|
+
@paths.map { |p| Regexp.escape(p) }.join('|')
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def relative_path_for(file)
|
21
|
+
path = Pathname.new(file)
|
22
|
+
if file[@pwd.to_s]
|
23
|
+
path.relative_path_from(@pwd)
|
24
|
+
elsif gem_root = @gem_roots.detect { |r| file[r.to_s] }
|
25
|
+
path.relative_path_from(gem_root)
|
26
|
+
else
|
27
|
+
file
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/benchmarkable'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'rblineprof'
|
6
|
+
require 'railsprof/paths'
|
7
|
+
require 'railsprof/lineprof_parser'
|
8
|
+
|
9
|
+
class Railsprof::Profiler
|
10
|
+
attr_reader :logger, :options
|
11
|
+
attr_accessor :gem_roots
|
12
|
+
|
13
|
+
def initialize(args, opts = {})
|
14
|
+
@options = Railsprof::DEFAULT_OPTIONS.merge(opts)
|
15
|
+
|
16
|
+
@logger = options[:logger] || Railsprof::Logger.new(STDOUT)
|
17
|
+
@logger.level = options[:log_level] || Logger::INFO
|
18
|
+
|
19
|
+
@paths = Railsprof::Paths.new(options.slice(:gems, :app_paths))
|
20
|
+
|
21
|
+
args.unshift('GET') if args.size == 1
|
22
|
+
|
23
|
+
if args.size == 2
|
24
|
+
args.first.upcase!
|
25
|
+
@method, @path = args
|
26
|
+
else
|
27
|
+
fail ArgumentError "excepted METHOD PATH, received: #{args.join(' ')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
logger.debug "Request: #@method #@path"
|
31
|
+
logger.debug "Options: #{options.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def profile!
|
35
|
+
load_app!
|
36
|
+
options[:warmups].times do |i|
|
37
|
+
say_with_time("Warmup ##{i + 1}") { run }
|
38
|
+
end
|
39
|
+
if options[:runs] > 0
|
40
|
+
logger.info 'Profiling....'
|
41
|
+
|
42
|
+
say_with_time('All profiles') do
|
43
|
+
@profile = lineprof(@paths.regexp) do
|
44
|
+
options[:runs].times do |i|
|
45
|
+
say_with_time("Profile ##{i + 1}") { run }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
parser = Railsprof::LineprofParser.new(
|
52
|
+
@profile, @paths,
|
53
|
+
threshold_ms: options[:threshold]
|
54
|
+
)
|
55
|
+
|
56
|
+
parser.cli_report
|
57
|
+
parser.html_report
|
58
|
+
end
|
59
|
+
|
60
|
+
def run
|
61
|
+
load_app!
|
62
|
+
|
63
|
+
begin
|
64
|
+
ret = Rails.application.routes.call(mock_request)
|
65
|
+
rescue Exception => e
|
66
|
+
if logger.level == Logger::DEBUG
|
67
|
+
raise e
|
68
|
+
else
|
69
|
+
logger.info "#{e.class.name} raised: #{e.message}"
|
70
|
+
logger.info "run railsprof with -v to see stacktrace"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
status, _ = ret
|
76
|
+
logger.warn "Status code #{status} received" if status != 200
|
77
|
+
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def mock_request
|
84
|
+
Rack::MockRequest.env_for(
|
85
|
+
@path,
|
86
|
+
method: @method,
|
87
|
+
params: options[:params]
|
88
|
+
# input: form body data
|
89
|
+
)
|
90
|
+
.merge({
|
91
|
+
# 'rack.session' => options[:session]
|
92
|
+
})
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_app!
|
96
|
+
return if defined? @loaded
|
97
|
+
|
98
|
+
logger.info 'App loading...'
|
99
|
+
env_file = Dir.pwd + '/config/environment.rb'
|
100
|
+
if File.exists?(env_file)
|
101
|
+
ms = Benchmark.ms { load env_file }
|
102
|
+
logger.info 'App loaded in %.2f secs' % (ms / 1000.0)
|
103
|
+
else
|
104
|
+
puts 'Exiting... an application with config/environment.rb was expected'
|
105
|
+
exit 1
|
106
|
+
end
|
107
|
+
@loaded = true
|
108
|
+
end
|
109
|
+
|
110
|
+
def say_with_time(msg, level: 'info', &block)
|
111
|
+
ret = nil
|
112
|
+
ms = Benchmark.ms { ret = block.call }
|
113
|
+
logger.send(level, '%s completed in %.2fms' % [msg, ms])
|
114
|
+
ret
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
=begin
|
119
|
+
namespace :api do
|
120
|
+
GEMS = %w[hasherdashery active_interaction]
|
121
|
+
APP_DIRS = %w[lib app config gems]
|
122
|
+
|
123
|
+
desc 'profile an API endpoint, return rblineprof data'
|
124
|
+
|
125
|
+
task :profile, [:route] => :environment do |t, args|
|
126
|
+
route = args[:route]
|
127
|
+
status, headers, body = nil
|
128
|
+
gem_roots = Set.new
|
129
|
+
|
130
|
+
paths =
|
131
|
+
APP_DIRS.map { |d| Rails.root.join(d).to_s } +
|
132
|
+
GEMS.map { |g|
|
133
|
+
gem_dir = Gem::Specification.find_by_name(g).gem_dir
|
134
|
+
gem_roots << Pathname.new(gem_dir).parent
|
135
|
+
gem_dir
|
136
|
+
}
|
137
|
+
|
138
|
+
path_pattern = Regexp.new paths.map { |p| Regexp.escape(p) }.join('|')
|
139
|
+
|
140
|
+
print "Warming up request... "
|
141
|
+
warmup_ms = Benchmark.ms { status, headers, body = internal_request(route) }
|
142
|
+
printf "finished in %dms with status %s\n" % [warmup_ms, status]
|
143
|
+
|
144
|
+
|
145
|
+
profile = lineprof(path_pattern) do
|
146
|
+
status, headers, body = nil
|
147
|
+
print "Profiling request... "
|
148
|
+
profiled_ms = Benchmark.ms { status, headers, body = internal_request(route) }
|
149
|
+
printf "finished in %dms with status %s\n" % [profiled_ms, status]
|
150
|
+
end
|
151
|
+
|
152
|
+
# rblineprof formatting
|
153
|
+
# {
|
154
|
+
# "/path/to/file" => [
|
155
|
+
# # File stats
|
156
|
+
# [total_time, child_time, exclusive_time, allocations],
|
157
|
+
# # Line 1 stats
|
158
|
+
# [wall, cpu, calls, allocations]
|
159
|
+
# # Line 2 stats
|
160
|
+
# [wall, cpu, calls, allocations]
|
161
|
+
# ...
|
162
|
+
# ]
|
163
|
+
# }
|
164
|
+
|
165
|
+
friendly_paths = {}
|
166
|
+
profiled_source = {}
|
167
|
+
|
168
|
+
profiled_files = profile
|
169
|
+
.inject([]) do |files, (file, ((total, _, exclusive, allocs), _))|
|
170
|
+
if total > 500 && !file[/benchmark|\.rake$/] # time in micros
|
171
|
+
files << [file, total, exclusive, allocs]
|
172
|
+
|
173
|
+
path = Pathname.new(file)
|
174
|
+
friendly_paths[file] =
|
175
|
+
if file[Rails.root.to_s]
|
176
|
+
path.relative_path_from(Rails.root)
|
177
|
+
elsif gem_root = gem_roots.detect { |r| file[r.to_s] }
|
178
|
+
path.relative_path_from(gem_root)
|
179
|
+
else
|
180
|
+
file
|
181
|
+
end
|
182
|
+
|
183
|
+
profiled_source[file] = "\n"
|
184
|
+
File.readlines(file).each_with_index do |line, num|
|
185
|
+
wall, cpu, calls, allocations = profile[file][num + 1]
|
186
|
+
profiled_source[file] <<
|
187
|
+
if calls && calls > 0
|
188
|
+
'% 8.1fms + % 8.1fms (% 5d) | %s' %
|
189
|
+
[cpu / 1000.0, (wall - cpu) / 1000.0, calls, line]
|
190
|
+
else
|
191
|
+
' | %s' % line
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
files
|
196
|
+
end
|
197
|
+
.sort_by { |f| -f[1] }
|
198
|
+
|
199
|
+
puts "\n-- Top files by execution time (ms / filename) --\n"
|
200
|
+
|
201
|
+
profiled_files.each do |file, total, exclusive, allocs|
|
202
|
+
printf "%8.3fms total %8.3fms excl %9d allocs %s \n" %
|
203
|
+
[total / 1000.0, exclusive / 1000.0, allocs, friendly_paths[file]]
|
204
|
+
end
|
205
|
+
|
206
|
+
filename = [
|
207
|
+
'/tmp/railsprof',
|
208
|
+
Rails.root.basename,
|
209
|
+
Time.now.to_s(:number),
|
210
|
+
].join('-') + '.html'
|
211
|
+
|
212
|
+
b = binding
|
213
|
+
template = File.read('railsprof-tmpl.html.erb')
|
214
|
+
ERB.new(template, 0, "", "@html_output").result(b)
|
215
|
+
|
216
|
+
File.open(filename, 'w') { |f| f.write(@html_output) }
|
217
|
+
|
218
|
+
`open #{filename}`
|
219
|
+
end
|
220
|
+
|
221
|
+
def internal_request(path, params={})
|
222
|
+
request_env = Rack::MockRequest.env_for(path, params: params.to_query).merge({
|
223
|
+
# 'rack.session' => session
|
224
|
+
})
|
225
|
+
|
226
|
+
Rails.application.routes.call(request_env)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
=end
|
230
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= Rails.root.basename %> railsprof run</title>
|
5
|
+
<link href="http://clifton.is/css/normalize.css" rel="stylesheet" type="text/css">
|
6
|
+
<link href="http://clifton.is/css/highlight.github.css" rel="stylesheet" type="text/css">
|
7
|
+
<style>
|
8
|
+
pre {
|
9
|
+
overflow: auto;
|
10
|
+
word-wrap: normal;
|
11
|
+
white-space: pre;
|
12
|
+
}
|
13
|
+
</style>
|
14
|
+
</head>
|
15
|
+
<body>
|
16
|
+
<table>
|
17
|
+
<thead>
|
18
|
+
<tr>
|
19
|
+
<td width='120px'>Wall Total</td>
|
20
|
+
<td width='110px'>Wall Child</td>
|
21
|
+
<td width='110px'>Wall Excl</td>
|
22
|
+
<td width='90px'>CPU Total</td>
|
23
|
+
<td width='90px'>CPU Child</td>
|
24
|
+
<td width='90px'>CPU Excl</td>
|
25
|
+
<td>File</td>
|
26
|
+
</tr>
|
27
|
+
</thead>
|
28
|
+
<tbody>
|
29
|
+
<% @profiled_files.each do |file, *times| %>
|
30
|
+
<tr>
|
31
|
+
<% times.each do |t| %>
|
32
|
+
<td><%= t / 1000.0 %></td>
|
33
|
+
<% end %>
|
34
|
+
<td>
|
35
|
+
<a href='#' class='js-toggle-code' data-file='<%= file %>'>
|
36
|
+
<%= @friendly_paths[file] %>
|
37
|
+
</a>
|
38
|
+
</td>
|
39
|
+
</tr>
|
40
|
+
<tr style='display:none'>
|
41
|
+
<td colspan='7' style='overflow: auto'>
|
42
|
+
<br style='clear:both'>
|
43
|
+
<pre>
|
44
|
+
<code class="ruby"><%= @profiled_source[file] %></code>
|
45
|
+
</pre>
|
46
|
+
</td>
|
47
|
+
</tr>
|
48
|
+
<% end %>
|
49
|
+
</tbody>
|
50
|
+
</table>
|
51
|
+
<script src='http://code.jquery.com/jquery-1.11.0.min.js'></script>
|
52
|
+
<script src='http://clifton.is/js/highlight.js'></script>
|
53
|
+
<script>
|
54
|
+
hljs.initHighlightingOnLoad();
|
55
|
+
$('.js-toggle-code').on('click', function () {
|
56
|
+
$(this).parents('tr').next().toggle();
|
57
|
+
return false;
|
58
|
+
});
|
59
|
+
</script>
|
60
|
+
</body>
|
61
|
+
</html>
|
data/lib/railsprof.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'railsprof/version'
|
2
|
+
require 'railsprof/logger'
|
3
|
+
require 'railsprof/profiler'
|
4
|
+
|
5
|
+
module Railsprof
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
session: {},
|
8
|
+
cookies: {},
|
9
|
+
params: {},
|
10
|
+
warmups: 1,
|
11
|
+
runs: 1,
|
12
|
+
log_level: Logger::INFO,
|
13
|
+
threshold: 0.5,
|
14
|
+
app_paths: %w(app lib config),
|
15
|
+
gems: []
|
16
|
+
}
|
17
|
+
end
|
data/railsprof.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'railsprof/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'railsprof'
|
8
|
+
spec.version = Railsprof::VERSION
|
9
|
+
spec.authors = ['Clifton King']
|
10
|
+
spec.email = ['cliftonk@gmail.com']
|
11
|
+
spec.summary = 'command line rbline prof for rails apps'
|
12
|
+
# spec.description = %q{TODO: Write a longer description. Optional.}
|
13
|
+
spec.homepage = 'http://orgsync.github.io'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split("\n")
|
17
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
spec.test_files = `git ls-files -- spec/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '> 1.3'
|
22
|
+
# spec.add_development_dependency 'rspec', '~> 1.5'
|
23
|
+
spec.add_runtime_dependency 'rake', '~> 10.1.1'
|
24
|
+
spec.add_runtime_dependency 'rails', '> 3.0'
|
25
|
+
spec.add_runtime_dependency 'activesupport', '> 3.0'
|
26
|
+
spec.add_runtime_dependency 'rblineprof', '~> 0.3.6'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: railsprof
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clifton King
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>'
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>'
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 10.1.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 10.1.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>'
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>'
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>'
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>'
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rblineprof
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.3.6
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.3.6
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- cliftonk@gmail.com
|
86
|
+
executables:
|
87
|
+
- railsprof
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/railsprof
|
97
|
+
- lib/railsprof.rb
|
98
|
+
- lib/railsprof/cli.rb
|
99
|
+
- lib/railsprof/lineprof_parser.rb
|
100
|
+
- lib/railsprof/logger.rb
|
101
|
+
- lib/railsprof/paths.rb
|
102
|
+
- lib/railsprof/profiler.rb
|
103
|
+
- lib/railsprof/version.rb
|
104
|
+
- lib/railsprof/views/railsprof-tmpl.html.erb
|
105
|
+
- railsprof.gemspec
|
106
|
+
homepage: http://orgsync.github.io
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 2.1.11
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: command line rbline prof for rails apps
|
130
|
+
test_files: []
|