railsprof 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+

|
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: []
|