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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +15 -0
  3. data/README.rdoc +36 -5
  4. data/bin/ruby-prof +7 -2
  5. data/doc/LICENSE.html +2 -1
  6. data/doc/README_rdoc.html +42 -8
  7. data/doc/Rack.html +2 -1
  8. data/doc/Rack/RubyProf.html +25 -18
  9. data/doc/Rack/RubyProf/RackProfiler.html +343 -0
  10. data/doc/RubyProf.html +14 -2
  11. data/doc/RubyProf/AbstractPrinter.html +91 -12
  12. data/doc/RubyProf/AggregateCallInfo.html +2 -1
  13. data/doc/RubyProf/CallInfo.html +18 -78
  14. data/doc/RubyProf/CallInfoPrinter.html +2 -1
  15. data/doc/RubyProf/CallInfoVisitor.html +2 -1
  16. data/doc/RubyProf/CallStackPrinter.html +35 -29
  17. data/doc/RubyProf/CallTreePrinter.html +98 -14
  18. data/doc/RubyProf/Cmd.html +11 -5
  19. data/doc/RubyProf/DeprecationWarnings.html +148 -0
  20. data/doc/RubyProf/DotPrinter.html +2 -1
  21. data/doc/RubyProf/FlatPrinter.html +2 -1
  22. data/doc/RubyProf/FlatPrinterWithLineNumbers.html +7 -5
  23. data/doc/RubyProf/GraphHtmlPrinter.html +18 -12
  24. data/doc/RubyProf/GraphPrinter.html +2 -1
  25. data/doc/RubyProf/MethodInfo.html +19 -88
  26. data/doc/RubyProf/MultiPrinter.html +231 -17
  27. data/doc/RubyProf/Profile.html +184 -39
  28. data/doc/RubyProf/Profile/ExcludeCommonMethods.html +411 -0
  29. data/doc/RubyProf/Profile/LegacyMethodElimination.html +158 -0
  30. data/doc/RubyProf/ProfileTask.html +2 -1
  31. data/doc/RubyProf/Thread.html +4 -39
  32. data/doc/created.rid +21 -19
  33. data/doc/css/fonts.css +6 -6
  34. data/doc/examples/flat_txt.html +2 -1
  35. data/doc/examples/graph_html.html +2 -1
  36. data/doc/examples/graph_txt.html +2 -1
  37. data/doc/index.html +47 -7
  38. data/doc/js/darkfish.js +7 -7
  39. data/doc/js/search_index.js +1 -1
  40. data/doc/js/search_index.js.gz +0 -0
  41. data/doc/js/searcher.js +1 -0
  42. data/doc/js/searcher.js.gz +0 -0
  43. data/doc/table_of_contents.html +190 -80
  44. data/ext/ruby_prof/extconf.rb +4 -0
  45. data/ext/ruby_prof/rp_call_info.c +19 -1
  46. data/ext/ruby_prof/rp_call_info.h +8 -3
  47. data/ext/ruby_prof/rp_method.c +282 -57
  48. data/ext/ruby_prof/rp_method.h +28 -5
  49. data/ext/ruby_prof/rp_stack.c +69 -24
  50. data/ext/ruby_prof/rp_stack.h +21 -9
  51. data/ext/ruby_prof/rp_thread.c +4 -1
  52. data/ext/ruby_prof/ruby_prof.c +142 -39
  53. data/ext/ruby_prof/ruby_prof.h +3 -0
  54. data/lib/ruby-prof.rb +10 -0
  55. data/lib/ruby-prof/call_info.rb +0 -11
  56. data/lib/ruby-prof/method_info.rb +4 -12
  57. data/lib/ruby-prof/printers/abstract_printer.rb +19 -1
  58. data/lib/ruby-prof/printers/call_info_printer.rb +1 -1
  59. data/lib/ruby-prof/printers/call_stack_printer.rb +9 -4
  60. data/lib/ruby-prof/printers/call_tree_printer.rb +15 -2
  61. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +23 -4
  62. data/lib/ruby-prof/printers/graph_html_printer.rb +10 -5
  63. data/lib/ruby-prof/printers/graph_printer.rb +2 -2
  64. data/lib/ruby-prof/printers/multi_printer.rb +44 -18
  65. data/lib/ruby-prof/profile.rb +13 -42
  66. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  67. data/lib/ruby-prof/profile/legacy_method_elimination.rb +49 -0
  68. data/lib/ruby-prof/rack.rb +130 -51
  69. data/lib/ruby-prof/thread.rb +0 -6
  70. data/lib/ruby-prof/version.rb +1 -1
  71. data/ruby-prof.gemspec +4 -3
  72. data/test/aggregate_test.rb +1 -1
  73. data/test/exclude_methods_test.rb +146 -0
  74. data/test/line_number_test.rb +12 -3
  75. data/test/multi_printer_test.rb +23 -2
  76. data/test/no_method_class_test.rb +1 -1
  77. data/test/printers_test.rb +21 -1
  78. data/test/rack_test.rb +64 -0
  79. data/test/recursive_test.rb +15 -15
  80. data/test/test_helper.rb +11 -0
  81. 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
@@ -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
- @tmpdir = options[:path] || Dir.tmpdir
12
- FileUtils.mkdir_p(@tmpdir)
9
+ options[:min_percent] ||= 1
13
10
 
14
- @printer_klasses = @options[:printers] || {::RubyProf::FlatPrinter => 'flat.txt',
15
- ::RubyProf::GraphPrinter => 'graph.txt',
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 = nil
29
- data = ::RubyProf::Profile.profile(profiling_options) do
30
- result = @app.call(env)
31
- end
28
+ result = @app.call(env)
29
+ ensure
30
+ profiler.pause
31
+ end
32
32
 
33
- path = request.path.gsub('/', '-')
34
- path.slice!(0)
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(data, path)
37
- result
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
- def should_profile?(path)
47
- return false if paths_match?(path, @skip_paths)
52
+ class RackProfiler
53
+ def initialize(options)
54
+ @options = options
48
55
 
49
- @only_paths ? paths_match?(path, @only_paths) : true
50
- end
56
+ @profile = ::RubyProf::Profile.new(profiling_options)
57
+ @profile.start
58
+ @profile.pause
51
59
 
52
- def paths_match?(path, paths)
53
- paths.any? { |skip_path| skip_path =~ path }
54
- end
60
+ @printer_klasses = options[:printers] || default_printers
55
61
 
56
- def profiling_options
57
- options = {}
58
- options[:measure_mode] = ::RubyProf.measure_mode
59
- options[:exclude_threads] =
60
- if @options[:ignore_existing_threads]
61
- Thread.list.select{|t| t != Thread.current}
62
- else
63
- ::RubyProf.exclude_threads
64
- end
65
- if @options[:request_thread_only]
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
- if @options[:merge_fibers]
69
- options[:merge_fibers] = true
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
- def print(data, path)
75
- @printer_klasses.each do |printer_klass, base_name|
76
- printer = printer_klass.new(data)
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
- if base_name.respond_to?(:call)
79
- base_name = base_name.call
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
- if printer_klass == ::RubyProf::MultiPrinter
83
- printer.print(@options.merge(:profile => "#{path}-#{base_name}"))
84
- elsif printer_klass == ::RubyProf::CallTreePrinter
85
- printer.print(@options.merge(:profile => "#{path}-#{base_name}"))
86
- else
87
- file_name = ::File.join(@tmpdir, "#{path}-#{base_name}")
88
- ::File.open(file_name, 'wb') do |file|
89
- printer.print(file, @options)
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