ruby-prof 0.16.2 → 0.17.0
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 +4 -4
- data/CHANGES +15 -0
- data/README.rdoc +36 -5
- data/bin/ruby-prof +7 -2
- data/doc/LICENSE.html +2 -1
- data/doc/README_rdoc.html +42 -8
- data/doc/Rack.html +2 -1
- data/doc/Rack/RubyProf.html +25 -18
- data/doc/Rack/RubyProf/RackProfiler.html +343 -0
- data/doc/RubyProf.html +14 -2
- data/doc/RubyProf/AbstractPrinter.html +91 -12
- data/doc/RubyProf/AggregateCallInfo.html +2 -1
- data/doc/RubyProf/CallInfo.html +18 -78
- data/doc/RubyProf/CallInfoPrinter.html +2 -1
- data/doc/RubyProf/CallInfoVisitor.html +2 -1
- data/doc/RubyProf/CallStackPrinter.html +35 -29
- data/doc/RubyProf/CallTreePrinter.html +98 -14
- data/doc/RubyProf/Cmd.html +11 -5
- data/doc/RubyProf/DeprecationWarnings.html +148 -0
- data/doc/RubyProf/DotPrinter.html +2 -1
- data/doc/RubyProf/FlatPrinter.html +2 -1
- data/doc/RubyProf/FlatPrinterWithLineNumbers.html +7 -5
- data/doc/RubyProf/GraphHtmlPrinter.html +18 -12
- data/doc/RubyProf/GraphPrinter.html +2 -1
- data/doc/RubyProf/MethodInfo.html +19 -88
- data/doc/RubyProf/MultiPrinter.html +231 -17
- data/doc/RubyProf/Profile.html +184 -39
- data/doc/RubyProf/Profile/ExcludeCommonMethods.html +411 -0
- data/doc/RubyProf/Profile/LegacyMethodElimination.html +158 -0
- data/doc/RubyProf/ProfileTask.html +2 -1
- data/doc/RubyProf/Thread.html +4 -39
- data/doc/created.rid +21 -19
- data/doc/css/fonts.css +6 -6
- data/doc/examples/flat_txt.html +2 -1
- data/doc/examples/graph_html.html +2 -1
- data/doc/examples/graph_txt.html +2 -1
- data/doc/index.html +47 -7
- data/doc/js/darkfish.js +7 -7
- data/doc/js/search_index.js +1 -1
- data/doc/js/search_index.js.gz +0 -0
- data/doc/js/searcher.js +1 -0
- data/doc/js/searcher.js.gz +0 -0
- data/doc/table_of_contents.html +190 -80
- data/ext/ruby_prof/extconf.rb +4 -0
- data/ext/ruby_prof/rp_call_info.c +19 -1
- data/ext/ruby_prof/rp_call_info.h +8 -3
- data/ext/ruby_prof/rp_method.c +282 -57
- data/ext/ruby_prof/rp_method.h +28 -5
- data/ext/ruby_prof/rp_stack.c +69 -24
- data/ext/ruby_prof/rp_stack.h +21 -9
- data/ext/ruby_prof/rp_thread.c +4 -1
- data/ext/ruby_prof/ruby_prof.c +142 -39
- data/ext/ruby_prof/ruby_prof.h +3 -0
- data/lib/ruby-prof.rb +10 -0
- data/lib/ruby-prof/call_info.rb +0 -11
- data/lib/ruby-prof/method_info.rb +4 -12
- data/lib/ruby-prof/printers/abstract_printer.rb +19 -1
- data/lib/ruby-prof/printers/call_info_printer.rb +1 -1
- data/lib/ruby-prof/printers/call_stack_printer.rb +9 -4
- data/lib/ruby-prof/printers/call_tree_printer.rb +15 -2
- data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +23 -4
- data/lib/ruby-prof/printers/graph_html_printer.rb +10 -5
- data/lib/ruby-prof/printers/graph_printer.rb +2 -2
- data/lib/ruby-prof/printers/multi_printer.rb +44 -18
- data/lib/ruby-prof/profile.rb +13 -42
- data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
- data/lib/ruby-prof/profile/legacy_method_elimination.rb +49 -0
- data/lib/ruby-prof/rack.rb +130 -51
- data/lib/ruby-prof/thread.rb +0 -6
- data/lib/ruby-prof/version.rb +1 -1
- data/ruby-prof.gemspec +4 -3
- data/test/aggregate_test.rb +1 -1
- data/test/exclude_methods_test.rb +146 -0
- data/test/line_number_test.rb +12 -3
- data/test/multi_printer_test.rb +23 -2
- data/test/no_method_class_test.rb +1 -1
- data/test/printers_test.rb +21 -1
- data/test/rack_test.rb +64 -0
- data/test/recursive_test.rb +15 -15
- data/test/test_helper.rb +11 -0
- metadata +20 -13
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module RubyProf
|
4
|
+
class Profile
|
5
|
+
class ExcludeCommonMethods
|
6
|
+
ENUMERABLE_NAMES = Enumerable.instance_methods(false)
|
7
|
+
|
8
|
+
def self.apply!(profile)
|
9
|
+
new(profile).apply!
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(profile)
|
13
|
+
@profile = profile
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply!
|
17
|
+
##
|
18
|
+
# Kernel Methods
|
19
|
+
##
|
20
|
+
|
21
|
+
exclude_methods Kernel, [
|
22
|
+
:dup,
|
23
|
+
:initialize_dup,
|
24
|
+
:tap,
|
25
|
+
:send,
|
26
|
+
:public_send,
|
27
|
+
]
|
28
|
+
|
29
|
+
##
|
30
|
+
# Fundamental Types
|
31
|
+
##
|
32
|
+
|
33
|
+
exclude_methods BasicObject, :"!="
|
34
|
+
exclude_methods Method, :"[]"
|
35
|
+
exclude_methods Module, :new
|
36
|
+
exclude_methods Class, :new
|
37
|
+
exclude_methods Proc, :call, :yield
|
38
|
+
exclude_methods Range, :each
|
39
|
+
exclude_methods Integer, :times
|
40
|
+
|
41
|
+
##
|
42
|
+
# Value Types
|
43
|
+
##
|
44
|
+
|
45
|
+
exclude_methods String, [
|
46
|
+
:sub,
|
47
|
+
:sub!,
|
48
|
+
:gsub,
|
49
|
+
:gsub!,
|
50
|
+
]
|
51
|
+
|
52
|
+
##
|
53
|
+
# Emumerables
|
54
|
+
##
|
55
|
+
|
56
|
+
exclude_enumerable Enumerable
|
57
|
+
exclude_enumerable Enumerator
|
58
|
+
|
59
|
+
##
|
60
|
+
# Collections
|
61
|
+
##
|
62
|
+
|
63
|
+
exclude_enumerable Array, [
|
64
|
+
:each_index,
|
65
|
+
:map!,
|
66
|
+
:select!,
|
67
|
+
:reject!,
|
68
|
+
:collect!,
|
69
|
+
:sort!,
|
70
|
+
:sort_by!,
|
71
|
+
:index,
|
72
|
+
:delete_if,
|
73
|
+
:keep_if,
|
74
|
+
:drop_while,
|
75
|
+
:uniq,
|
76
|
+
:uniq!,
|
77
|
+
:"==",
|
78
|
+
:eql?,
|
79
|
+
:hash,
|
80
|
+
:to_json,
|
81
|
+
:as_json,
|
82
|
+
:encode_json,
|
83
|
+
]
|
84
|
+
|
85
|
+
exclude_enumerable Hash, [
|
86
|
+
:dup,
|
87
|
+
:initialize_dup,
|
88
|
+
:fetch,
|
89
|
+
:"[]",
|
90
|
+
:"[]=",
|
91
|
+
:each_key,
|
92
|
+
:each_value,
|
93
|
+
:each_pair,
|
94
|
+
:map!,
|
95
|
+
:select!,
|
96
|
+
:reject!,
|
97
|
+
:collect!,
|
98
|
+
:delete_if,
|
99
|
+
:keep_if,
|
100
|
+
:slice,
|
101
|
+
:slice!,
|
102
|
+
:except,
|
103
|
+
:except!,
|
104
|
+
:"==",
|
105
|
+
:eql?,
|
106
|
+
:hash,
|
107
|
+
:to_json,
|
108
|
+
:as_json,
|
109
|
+
:encode_json,
|
110
|
+
]
|
111
|
+
|
112
|
+
exclude_enumerable Set, [
|
113
|
+
:map!,
|
114
|
+
:select!,
|
115
|
+
:reject!,
|
116
|
+
:collect!,
|
117
|
+
:classify,
|
118
|
+
:delete_if,
|
119
|
+
:keep_if,
|
120
|
+
:divide,
|
121
|
+
:"==",
|
122
|
+
:eql?,
|
123
|
+
:hash,
|
124
|
+
:to_json,
|
125
|
+
:as_json,
|
126
|
+
:encode_json,
|
127
|
+
]
|
128
|
+
|
129
|
+
##
|
130
|
+
# Garbage Collection
|
131
|
+
##
|
132
|
+
|
133
|
+
exclude_singleton_methods GC, [
|
134
|
+
:start
|
135
|
+
]
|
136
|
+
|
137
|
+
##
|
138
|
+
# Unicorn
|
139
|
+
##
|
140
|
+
|
141
|
+
if defined?(Unicorn)
|
142
|
+
exclude_methods Unicorn::HttpServer, :process_client
|
143
|
+
end
|
144
|
+
|
145
|
+
if defined?(Unicorn::OobGC)
|
146
|
+
exclude_methods Unicorn::OobGC, :process_client
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# New Relic
|
151
|
+
##
|
152
|
+
|
153
|
+
if defined?(NewRelic)
|
154
|
+
exclude_methods NewRelic::Agent::Instrumentation::MiddlewareTracing, [
|
155
|
+
:call
|
156
|
+
]
|
157
|
+
|
158
|
+
exclude_methods NewRelic::Agent::MethodTracerHelpers, [
|
159
|
+
:trace_execution_scoped,
|
160
|
+
:log_errors,
|
161
|
+
]
|
162
|
+
|
163
|
+
exclude_singleton_methods NewRelic::Agent::MethodTracerHelpers, [
|
164
|
+
:trace_execution_scoped,
|
165
|
+
:log_errors,
|
166
|
+
]
|
167
|
+
|
168
|
+
exclude_methods NewRelic::Agent::MethodTracer, [
|
169
|
+
:trace_execution_scoped,
|
170
|
+
:trace_execution_unscoped,
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Miscellaneous Methods
|
176
|
+
##
|
177
|
+
|
178
|
+
if defined?(Mustache)
|
179
|
+
exclude_methods Mustache::Context, [
|
180
|
+
:fetch
|
181
|
+
]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def exclude_methods(mod, *method_or_methods)
|
188
|
+
@profile.exclude_methods!(mod, method_or_methods)
|
189
|
+
end
|
190
|
+
|
191
|
+
def exclude_singleton_methods(mod, *method_or_methods)
|
192
|
+
@profile.exclude_singleton_methods!(mod, method_or_methods)
|
193
|
+
end
|
194
|
+
|
195
|
+
def exclude_enumerable(mod, *method_or_methods)
|
196
|
+
exclude_methods(mod, [:each, *method_or_methods])
|
197
|
+
exclude_methods(mod, ENUMERABLE_NAMES)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RubyProf
|
2
|
+
class Profile
|
3
|
+
module LegacyMethodElimination
|
4
|
+
# eliminate some calls from the graph by merging the information into callers.
|
5
|
+
# matchers can be a list of strings or regular expressions or the name of a file containing regexps.
|
6
|
+
def eliminate_methods!(matchers)
|
7
|
+
RubyProf.deprecation_warning(
|
8
|
+
"Method 'eliminate_methods!' is dprecated",
|
9
|
+
"Please call 'exclude_methods!' before starting the profile run instead."
|
10
|
+
)
|
11
|
+
matchers = read_regexps_from_file(matchers) if matchers.is_a?(String)
|
12
|
+
eliminated = []
|
13
|
+
threads.each do |thread|
|
14
|
+
matchers.each{ |matcher| eliminated.concat(eliminate_methods(thread.methods, matcher)) }
|
15
|
+
end
|
16
|
+
eliminated
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# read regexps from file
|
22
|
+
def read_regexps_from_file(file_name)
|
23
|
+
matchers = []
|
24
|
+
File.open(file_name).each_line do |l|
|
25
|
+
next if (l =~ /^(#.*|\s*)$/) # emtpy lines and lines starting with #
|
26
|
+
matchers << Regexp.new(l.strip)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# eliminate methods matching matcher
|
31
|
+
def eliminate_methods(methods, matcher)
|
32
|
+
eliminated = []
|
33
|
+
i = 0
|
34
|
+
while i < methods.size
|
35
|
+
method_info = methods[i]
|
36
|
+
method_name = method_info.full_name
|
37
|
+
if matcher === method_name
|
38
|
+
raise "can't eliminate root method" if method_info.root?
|
39
|
+
eliminated << methods.delete_at(i)
|
40
|
+
method_info.eliminate!
|
41
|
+
else
|
42
|
+
i += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
eliminated
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/ruby-prof/rack.rb
CHANGED
@@ -5,37 +5,43 @@ module Rack
|
|
5
5
|
class RubyProf
|
6
6
|
def initialize(app, options = {})
|
7
7
|
@app = app
|
8
|
-
@options = options
|
9
|
-
@options[:min_percent] ||= 1
|
10
8
|
|
11
|
-
|
12
|
-
FileUtils.mkdir_p(@tmpdir)
|
9
|
+
options[:min_percent] ||= 1
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
::RubyProf::GraphHtmlPrinter => 'graph.html',
|
17
|
-
::RubyProf::CallStackPrinter => 'call_stack.html'}
|
11
|
+
options[:path] ||= Dir.tmpdir
|
12
|
+
FileUtils.mkdir_p(options[:path])
|
18
13
|
|
19
14
|
@skip_paths = options[:skip_paths] || [%r{^/assets}, %r{\.(css|js|png|jpeg|jpg|gif)$}]
|
20
15
|
@only_paths = options[:only_paths]
|
16
|
+
|
17
|
+
@max_requests = options[:max_requests]
|
18
|
+
|
19
|
+
@options = options
|
21
20
|
end
|
22
21
|
|
23
22
|
def call(env)
|
24
23
|
request = Rack::Request.new(env)
|
25
24
|
|
26
25
|
if should_profile?(request.path)
|
26
|
+
profiler.resume
|
27
27
|
begin
|
28
|
-
result =
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
result = @app.call(env)
|
29
|
+
ensure
|
30
|
+
profiler.pause
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
if profiler.max_requests_reached?
|
34
|
+
prefix = if aggregate_requests?
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
request.path.gsub('/', '-')[1..-1]
|
38
|
+
end
|
35
39
|
|
36
|
-
print(
|
37
|
-
|
40
|
+
profiler.print!(prefix)
|
41
|
+
delete_profiler!
|
38
42
|
end
|
43
|
+
|
44
|
+
result
|
39
45
|
else
|
40
46
|
@app.call(env)
|
41
47
|
end
|
@@ -43,53 +49,126 @@ module Rack
|
|
43
49
|
|
44
50
|
private
|
45
51
|
|
46
|
-
|
47
|
-
|
52
|
+
class RackProfiler
|
53
|
+
def initialize(options)
|
54
|
+
@options = options
|
48
55
|
|
49
|
-
|
50
|
-
|
56
|
+
@profile = ::RubyProf::Profile.new(profiling_options)
|
57
|
+
@profile.start
|
58
|
+
@profile.pause
|
51
59
|
|
52
|
-
|
53
|
-
paths.any? { |skip_path| skip_path =~ path }
|
54
|
-
end
|
60
|
+
@printer_klasses = options[:printers] || default_printers
|
55
61
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
options[:include_threads] = [Thread.current]
|
62
|
+
@tmpdir = options[:path]
|
63
|
+
|
64
|
+
@max_requests = options[:max_requests] || 1
|
65
|
+
@requests_count = 0
|
66
|
+
|
67
|
+
@printed = false
|
68
|
+
# if running across multiple requests, we want to make sure that the
|
69
|
+
# ongoing profile is not lost if the process shuts down before the
|
70
|
+
# max request count is reached
|
71
|
+
ObjectSpace.define_finalizer(self, proc { print! })
|
67
72
|
end
|
68
|
-
|
69
|
-
|
73
|
+
|
74
|
+
def resume
|
75
|
+
@profile.resume
|
76
|
+
end
|
77
|
+
|
78
|
+
def pause
|
79
|
+
@profile.pause
|
80
|
+
@requests_count += 1
|
70
81
|
end
|
71
|
-
options
|
72
|
-
end
|
73
82
|
|
74
|
-
|
75
|
-
|
76
|
-
|
83
|
+
def max_requests_reached?
|
84
|
+
@requests_count >= @max_requests
|
85
|
+
end
|
86
|
+
|
87
|
+
def print!(prefix = nil)
|
88
|
+
return false if @printed || @requests_count == 0
|
89
|
+
|
90
|
+
data = @profile.stop
|
91
|
+
|
92
|
+
prefix ||= "multi-requests-#{@requests_count}"
|
93
|
+
|
94
|
+
@printer_klasses.each do |printer_klass, base_name|
|
95
|
+
printer = printer_klass.new(data)
|
96
|
+
|
97
|
+
if base_name.respond_to?(:call)
|
98
|
+
base_name = base_name.call
|
99
|
+
end
|
77
100
|
|
78
|
-
|
79
|
-
|
101
|
+
if printer_klass == ::RubyProf::MultiPrinter \
|
102
|
+
|| printer_klass == ::RubyProf::CallTreePrinter
|
103
|
+
printer.print(@options.merge(:profile => "#{prefix}-#{base_name}"))
|
104
|
+
else
|
105
|
+
file_name = ::File.join(@tmpdir, "#{prefix}-#{base_name}")
|
106
|
+
::File.open(file_name, 'wb') do |file|
|
107
|
+
printer.print(file, @options)
|
108
|
+
end
|
109
|
+
end
|
80
110
|
end
|
81
111
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
112
|
+
@printed = true
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def profiling_options
|
118
|
+
options = {}
|
119
|
+
options[:measure_mode] = ::RubyProf.measure_mode
|
120
|
+
options[:exclude_threads] =
|
121
|
+
if @options[:ignore_existing_threads]
|
122
|
+
Thread.list.select{|t| t != Thread.current}
|
123
|
+
else
|
124
|
+
::RubyProf.exclude_threads
|
90
125
|
end
|
126
|
+
if @options[:request_thread_only]
|
127
|
+
options[:include_threads] = [Thread.current]
|
91
128
|
end
|
129
|
+
if @options[:merge_fibers]
|
130
|
+
options[:merge_fibers] = true
|
131
|
+
end
|
132
|
+
options
|
133
|
+
end
|
134
|
+
|
135
|
+
def default_printers
|
136
|
+
{::RubyProf::FlatPrinter => 'flat.txt',
|
137
|
+
::RubyProf::GraphPrinter => 'graph.txt',
|
138
|
+
::RubyProf::GraphHtmlPrinter => 'graph.html',
|
139
|
+
::RubyProf::CallStackPrinter => 'call_stack.html'}
|
92
140
|
end
|
93
141
|
end
|
142
|
+
|
143
|
+
def profiler
|
144
|
+
if aggregate_requests?
|
145
|
+
@@_shared_profiler ||= RackProfiler.new(@options)
|
146
|
+
else
|
147
|
+
@_profiler ||= RackProfiler.new(@options)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def delete_profiler!
|
152
|
+
if aggregate_requests?
|
153
|
+
@@_shared_profiler.print! if @@_shared_profiler
|
154
|
+
@@_shared_profiler = nil
|
155
|
+
else
|
156
|
+
@_profiler = nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def aggregate_requests?
|
161
|
+
!@max_requests.nil?
|
162
|
+
end
|
163
|
+
|
164
|
+
def should_profile?(path)
|
165
|
+
return false if paths_match?(path, @skip_paths)
|
166
|
+
|
167
|
+
@only_paths ? paths_match?(path, @only_paths) : true
|
168
|
+
end
|
169
|
+
|
170
|
+
def paths_match?(path, paths)
|
171
|
+
paths.any? { |skip_path| skip_path =~ path }
|
172
|
+
end
|
94
173
|
end
|
95
174
|
end
|