airbnb-ruby-prof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ruby-prof/profile/exclude_common_methods'
4
+ module RubyProf
5
+ class Profile
6
+ # Hides methods that, when represented as a call graph, have
7
+ # extremely large in and out degrees and make navigation impossible.
8
+ def exclude_common_methods!
9
+ ExcludeCommonMethods.apply!(self)
10
+ end
11
+
12
+ def exclude_methods!(mod, *method_or_methods)
13
+ [method_or_methods].flatten.each do |name|
14
+ exclude_method!(mod, name)
15
+ end
16
+ end
17
+
18
+ def exclude_singleton_methods!(mod, *method_or_methods)
19
+ exclude_methods!(mod.singleton_class, *method_or_methods)
20
+ end
21
+ end
22
+ end
@@ -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,95 @@
1
+ # encoding: utf-8
2
+ require 'tmpdir'
3
+
4
+ module Rack
5
+ class RubyProf
6
+ def initialize(app, options = {})
7
+ @app = app
8
+ @options = options
9
+ @options[:min_percent] ||= 1
10
+
11
+ @tmpdir = options[:path] || Dir.tmpdir
12
+ FileUtils.mkdir_p(@tmpdir)
13
+
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'}
18
+
19
+ @skip_paths = options[:skip_paths] || [%r{^/assets}, %r{\.(css|js|png|jpeg|jpg|gif)$}]
20
+ @only_paths = options[:only_paths]
21
+ end
22
+
23
+ def call(env)
24
+ request = Rack::Request.new(env)
25
+
26
+ if should_profile?(request.path)
27
+ begin
28
+ result = nil
29
+ data = ::RubyProf::Profile.profile(profiling_options) do
30
+ result = @app.call(env)
31
+ end
32
+
33
+ path = request.path.gsub('/', '-')
34
+ path.slice!(0)
35
+
36
+ print(data, path)
37
+ result
38
+ end
39
+ else
40
+ @app.call(env)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def should_profile?(path)
47
+ return false if paths_match?(path, @skip_paths)
48
+
49
+ @only_paths ? paths_match?(path, @only_paths) : true
50
+ end
51
+
52
+ def paths_match?(path, paths)
53
+ paths.any? { |skip_path| skip_path =~ path }
54
+ end
55
+
56
+ def profiling_options
57
+ options = {}
58
+ options[:measure_modes] = [::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]
67
+ end
68
+ if @options[:merge_fibers]
69
+ options[:merge_fibers] = true
70
+ end
71
+ options
72
+ end
73
+
74
+ def print(data, path)
75
+ @printer_klasses.each do |printer_klass, base_name|
76
+ printer = printer_klass.new(data)
77
+
78
+ if base_name.respond_to?(:call)
79
+ base_name = base_name.call
80
+ end
81
+
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)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'fileutils'
7
+
8
+ module RubyProf
9
+
10
+ # Define a task library for profiling unit tests with ruby-prof.
11
+ #
12
+ # All of the options provided by
13
+ # the Rake:TestTask are supported except the loader
14
+ # which is set to ruby-prof. For detailed information
15
+ # please refer to the Rake:TestTask documentation.
16
+ #
17
+ # ruby-prof specific options include:
18
+ #
19
+ # output_dir - For each file specified an output
20
+ # file with profile information will be
21
+ # written to the output directory.
22
+ # By default, the output directory is
23
+ # called "profile" and is created underneath
24
+ # the current working directory.
25
+ #
26
+ # printer - Specifies the output printer. Valid values include
27
+ # :flat, :graph, :graph_html and :call_tree.
28
+ #
29
+ # min_percent - Methods that take less than the specified percent
30
+ # will not be written out.
31
+ #
32
+ # Example:
33
+ #
34
+ # require 'ruby-prof/task'
35
+ #
36
+ # RubyProf::ProfileTask.new do |t|
37
+ # t.test_files = FileList['test/test*.rb']
38
+ # t.output_dir = "c:/temp"
39
+ # t.printer = :graph
40
+ # t.min_percent = 10
41
+ # end
42
+ #
43
+ # If rake is invoked with a "TEST=filename" command line option,
44
+ # then the list of test files will be overridden to include only the
45
+ # filename specified on the command line. This provides an easy way
46
+ # to run just one test.
47
+ #
48
+ # If rake is invoked with a "TESTOPTS=options" command line option,
49
+ # then the given options are passed to the test process after a
50
+ # '--'. This allows Test::Unit options to be passed to the test
51
+ # suite.
52
+ #
53
+ # Examples:
54
+ #
55
+ # rake profile # run tests normally
56
+ # rake profile TEST=just_one_file.rb # run just one test file.
57
+ # rake profile TESTOPTS="-v" # run in verbose mode
58
+ # rake profile TESTOPTS="--runner=fox" # use the fox test runner
59
+
60
+ class ProfileTask < Rake::TestTask
61
+ attr_accessor :output_dir
62
+ attr_accessor :min_percent
63
+ attr_accessor :printer
64
+
65
+ def initialize(name = :profile)
66
+ super(name)
67
+ end
68
+
69
+ # Create the tasks defined by this task lib.
70
+ def define
71
+ lib_path = @libs.join(File::PATH_SEPARATOR)
72
+ desc "Profile" + (@name==:profile ? "" : " for #{@name}")
73
+
74
+ task @name do
75
+ create_output_directory
76
+
77
+ @ruby_opts.unshift( "-I#{lib_path}" )
78
+ @ruby_opts.unshift( "-w" ) if @warning
79
+ @ruby_opts.push("-S ruby-prof")
80
+ @ruby_opts.push("--printer #{@printer}")
81
+ @ruby_opts.push("--min_percent #{@min_percent}")
82
+
83
+ file_list.each do |file_path|
84
+ run_script(file_path)
85
+ end
86
+ end
87
+ self
88
+ end
89
+
90
+ # Run script
91
+ def run_script(script_path)
92
+ run_code = ''
93
+ RakeFileUtils.verbose(@verbose) do
94
+ file_name = File.basename(script_path, File.extname(script_path))
95
+ case @printer
96
+ when :flat, :graph, :call_tree
97
+ file_name += ".txt"
98
+ when :graph_html
99
+ file_name += ".html"
100
+ else
101
+ file_name += ".txt"
102
+ end
103
+
104
+ output_file_path = File.join(output_directory, file_name)
105
+
106
+ command_line = @ruby_opts.join(" ") +
107
+ " --file=" + output_file_path +
108
+ " " + script_path
109
+
110
+ puts "ruby " + command_line
111
+ # We have to catch the exeption to continue on. However,
112
+ # the error message will have been output to STDERR
113
+ # already by the time we get here so we don't have to
114
+ # do that again
115
+ begin
116
+ ruby command_line
117
+ rescue => e
118
+ STDOUT << e << "\n"
119
+ STDOUT.flush
120
+ end
121
+ puts ""
122
+ puts ""
123
+ end
124
+ end
125
+
126
+ def output_directory
127
+ File.expand_path(@output_dir)
128
+ end
129
+
130
+ def create_output_directory
131
+ if not File.exist?(output_directory)
132
+ Dir.mkdir(output_directory)
133
+ end
134
+ end
135
+
136
+ def clean_output_directory
137
+ if File.exist?(output_directory)
138
+ files = Dir.glob(output_directory + '/*')
139
+ FileUtils.rm(files)
140
+ end
141
+ end
142
+
143
+ def option_list # :nodoc:
144
+ ENV['OPTIONS'] || @options.join(" ") || ""
145
+ end
146
+ end
147
+ end