airbnb-ruby-prof 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.
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,132 @@
1
+ # encoding: utf-8
2
+
3
+ require 'set'
4
+
5
+ module RubyProf
6
+ # Generates a graphviz graph in dot format.
7
+ # To use the dot printer:
8
+ #
9
+ # result = RubyProf.profile do
10
+ # [code to profile]
11
+ # end
12
+ #
13
+ # printer = RubyProf::DotPrinter.new(result)
14
+ # printer.print(STDOUT)
15
+ #
16
+ # You can use either dot viewer such as GraphViz, or the dot command line tool
17
+ # to reformat the output into a wide variety of outputs:
18
+ #
19
+ # dot -Tpng graph.dot > graph.png
20
+ #
21
+ class DotPrinter < RubyProf::AbstractPrinter
22
+ CLASS_COLOR = '"#666666"'
23
+ EDGE_COLOR = '"#666666"'
24
+
25
+ # Creates the DotPrinter using a RubyProf::Proile.
26
+ def initialize(result)
27
+ super(result)
28
+ @seen_methods = Set.new
29
+ end
30
+
31
+ # Print a graph report to the provided output.
32
+ #
33
+ # output - Any IO object, including STDOUT or a file. The default value is
34
+ # STDOUT.
35
+ #
36
+ # options - Hash of print options. See #setup_options
37
+ # for more information.
38
+ #
39
+ # When profiling results that cover a large number of method calls it
40
+ # helps to use the :min_percent option, for example:
41
+ #
42
+ # DotPrinter.new(result).print(STDOUT, :min_percent=>5)
43
+ #
44
+ def print(output = STDOUT, options = {})
45
+ @output = output
46
+ setup_options(options)
47
+
48
+ puts 'digraph "Profile" {'
49
+ #puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";"
50
+ puts "labelloc=t;"
51
+ puts "labeljust=l;"
52
+ print_threads
53
+ puts '}'
54
+ end
55
+
56
+ private
57
+
58
+ # Something of a hack, figure out which constant went with the
59
+ # RubyProf.measure_mode so that we can display it. Otherwise it's easy to
60
+ # forget what measurement was made.
61
+ def mode_name
62
+ RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode}
63
+ end
64
+
65
+ def print_threads
66
+ @result.threads.each do |thread|
67
+ puts "subgraph \"Thread #{thread.id}\" {"
68
+
69
+ print_thread(thread)
70
+ puts "}"
71
+
72
+ print_classes(thread)
73
+ end
74
+ end
75
+
76
+ # Determines an ID to use to represent the subject in the Dot file.
77
+ def dot_id(subject)
78
+ subject.object_id
79
+ end
80
+
81
+ def print_thread(thread)
82
+ total_time = thread.total_time
83
+ thread.methods.sort_by(&sort_method).reverse_each do |method|
84
+ total_percentage = (method.total_time/total_time) * 100
85
+
86
+ next if total_percentage < min_percent
87
+ name = method_name(method).split("#").last
88
+ puts "#{dot_id(method)} [label=\"#{name}\\n(#{total_percentage.round}%)\"];"
89
+ @seen_methods << method
90
+ print_edges(total_time, method)
91
+ end
92
+ end
93
+
94
+ def print_classes(thread)
95
+ grouped = {}
96
+ thread.methods.each{|m| grouped[m.klass_name] ||= []; grouped[m.klass_name] << m}
97
+ grouped.each do |cls, methods2|
98
+ # Filter down to just seen methods
99
+ big_methods = methods2.select{|m| @seen_methods.include? m}
100
+
101
+ if !big_methods.empty?
102
+ puts "subgraph cluster_#{cls.object_id} {"
103
+ puts "label = \"#{cls}\";"
104
+ puts "fontcolor = #{CLASS_COLOR};"
105
+ puts "fontsize = 16;"
106
+ puts "color = #{CLASS_COLOR};"
107
+ big_methods.each do |m|
108
+ puts "#{m.object_id};"
109
+ end
110
+ puts "}"
111
+ end
112
+ end
113
+ end
114
+
115
+ def print_edges(total_time, method)
116
+ method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
117
+
118
+ target_percentage = (child.target.total_time / total_time) * 100.0
119
+ next if target_percentage < min_percent
120
+
121
+ # Get children method
122
+ puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];"
123
+ end
124
+ end
125
+
126
+ # Silly little helper for printing to the @output
127
+ def puts(str)
128
+ @output.puts(str)
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ require 'fiber'
4
+ require 'thread'
5
+ require 'fileutils'
6
+
7
+ module RubyProf
8
+ # Optimized implementation of CallTreePrinter
9
+ class FastCallTreePrinter
10
+ def initialize(result)
11
+ @result = result
12
+ @output = nil
13
+ end
14
+
15
+ # Specify print options.
16
+ #
17
+ # options - Hash table
18
+ #
19
+ # :only_threads - list of threads to print
20
+ def print(output = STDOUT, options = {})
21
+ @output = output
22
+ @options = options
23
+
24
+ determine_event_specification_and_value_scale
25
+ print_headers
26
+ print_threads
27
+ end
28
+
29
+ def determine_event_specification_and_value_scale
30
+ @value_scales = []
31
+ @event_specifications = ['events:']
32
+
33
+ @result.measure_modes.each do |measure_mode|
34
+ case measure_mode
35
+ when RubyProf::PROCESS_TIME
36
+ @value_scales << RubyProf::CLOCKS_PER_SEC
37
+ @event_specifications << 'process_time'
38
+ when RubyProf::WALL_TIME
39
+ @value_scales << 1_000_000
40
+ @event_specifications << 'wall_time'
41
+ when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
42
+ @value_scales << RubyProf.cpu_frequency
43
+ @event_specifications << 'cpu_time'
44
+ when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
45
+ @value_scales << 1
46
+ @event_specifications << 'allocations'
47
+ when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
48
+ @value_scales << 1
49
+ @event_specifications << 'memory'
50
+ when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
51
+ @value_scales << 1
52
+ @event_specifications << 'gc_runs'
53
+ when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
54
+ @value_scales << 1000000
55
+ @event_specifications << 'gc_time'
56
+ else
57
+ raise "Unknown measure mode: #{measure_mode}"
58
+ end
59
+ end
60
+ end
61
+
62
+ def print_threads
63
+ # TODO: merge fibers of a given thread here, instead of relying
64
+ # on the profiler to merge fibers.
65
+ printable_threads.each do |thread|
66
+ print_thread(thread)
67
+ end
68
+ end
69
+
70
+ def printable_threads
71
+ if @options[:only_threads]
72
+ only_thread_ids = @options[:only_threads].map(&:object_id)
73
+ @result.threads.select do |t|
74
+ only_thread_ids.include?(t.id)
75
+ end
76
+ else
77
+ @result.threads
78
+ end
79
+ end
80
+
81
+ def print_headers
82
+ @output << "#{@event_specifications.join(" ")}\n\n"
83
+ # this doesn't work. kcachegrind does not fully support the spec.
84
+ # output << "thread: #{thread.id}\n\n"
85
+ end
86
+ end # end class
87
+ end # end packages
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+ require 'erb'
3
+
4
+ module RubyProf
5
+ class FlameGraphHtmlPrinter < AbstractPrinter
6
+ include ERB::Util
7
+
8
+ def setup_options(options={})
9
+ super(options)
10
+
11
+ ref = 'tmpl.html.erb'
12
+ template = read_asset(ref)
13
+ @erb = ERB.new(template)
14
+ @erb.filename = ref
15
+ end
16
+
17
+ def print(output=STDOUT, options={})
18
+ @output = output
19
+ setup_options(options)
20
+
21
+ str = @erb.result(binding)
22
+ @output << str.split("\n").map(&:rstrip).join("\n")
23
+ @output << "\n"
24
+ end
25
+
26
+ private
27
+
28
+ def css_libraries_html
29
+ read_asset('lib.css.html')
30
+ end
31
+
32
+ def js_libraries_html
33
+ read_asset('lib.js.html')
34
+ end
35
+
36
+ def js_code_html
37
+ read_asset('page.js.html')
38
+ end
39
+
40
+ def js_data_html
41
+ "<script type=\"text/javascript\">\n\n" \
42
+ "var data = #{data_json};\n\n" \
43
+ "</script>\n"
44
+ end
45
+
46
+ def data_json
47
+ StringIO.new.tap { |strio|
48
+ jp = FlameGraphJsonPrinter.new(@result)
49
+ jp.print(strio, @options)
50
+ }.string
51
+ end
52
+
53
+ def read_asset(ref)
54
+ base_path = File.expand_path('../../assets', __FILE__)
55
+ file_path = File.join(base_path, "flame_graph_printer.#{ref}")
56
+ File.open(file_path, 'rb').read.strip.untaint
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ class FlameGraphJsonPrinter < AbstractPrinter
5
+ def min_percent
6
+ @options[:min_percent] || 0.25
7
+ end
8
+
9
+ def min_self_time
10
+ @options[:min_self_time] || 0.001
11
+ end
12
+
13
+ def only_threads
14
+ @options[:only_threads]
15
+ end
16
+
17
+ def print_threads
18
+ @threads = @result.threads.dup
19
+ @threads.select! { |t| only_thread_ids.include?(t.id) } if only_threads
20
+
21
+ walker = FlameDataWalker.new(@output, @threads, {
22
+ min_percent: min_percent,
23
+ min_self_time: min_self_time
24
+ })
25
+
26
+ @output << "{\n"
27
+ @output << " \"root\": "
28
+
29
+ walker.run
30
+
31
+ @output << ",\n"
32
+ @output << " \"depth\": #{walker.height}\n"
33
+ @output << "}"
34
+ end
35
+
36
+ private
37
+
38
+ def only_thread_ids
39
+ only_threads && only_threads.map(&:object_id)
40
+ end
41
+
42
+ class FlameDataWalker < RubyProf::Walker
43
+ attr_reader :output
44
+ attr_reader :height
45
+
46
+ def initialize(output, threads, options={})
47
+ super(threads, options)
48
+ @output = output
49
+
50
+ @printer = FlameDataJsonPrinter.new(@output, {
51
+ depth: 1,
52
+ anchored: false
53
+ })
54
+
55
+ @height = 0
56
+ @depth = 0
57
+ end
58
+
59
+ def enter_frame(type, obj, name, called, self_value, total_value)
60
+ @depth += 1
61
+ @height = @depth if @height < @depth
62
+ @printer.enter(name, called, self_value, total_value)
63
+ end
64
+
65
+ def leave_frame(type, obj)
66
+ @printer.leave
67
+ @depth -= 1
68
+ end
69
+ end
70
+
71
+ class FlameDataJsonPrinter
72
+ attr_reader :output
73
+
74
+ def initialize(output, options={})
75
+ @output = output
76
+ @depth = options[:depth] || 0
77
+ @anchored = options.fetch(:anchored, false)
78
+ @pretty = options.fetch(:pretty, false)
79
+ @state = :root
80
+ update_layout
81
+ end
82
+
83
+ def enter(name, called, self_value, total_value)
84
+ case @state
85
+ when :enter
86
+ put_line ","
87
+ put_part "\"children\": ["
88
+ step_in(:enter)
89
+ when :leave
90
+ put_part ", "
91
+ end
92
+
93
+ put_line "{"
94
+ step_in(:enter)
95
+ put_line "\"name\": \"#{name}\","
96
+ put_line "\"called\": #{called},"
97
+ put_line "\"lost\": #{self_value},"
98
+ put_part "\"value\": #{total_value}"
99
+ end
100
+
101
+ def leave
102
+ case @state
103
+ when :enter
104
+ new_line
105
+ when :leave
106
+ step_out(:leave)
107
+ put_line "]"
108
+ end
109
+
110
+ step_out(:leave)
111
+ put_part "}"
112
+ end
113
+
114
+ private
115
+
116
+ def put_part(str)
117
+ @output << @indent if @anchored
118
+ @output << str
119
+ @anchored = false
120
+ end
121
+
122
+ def put_line(str)
123
+ @output << @indent if @anchored
124
+ @output << str
125
+ @output << @break
126
+ @anchored = true
127
+ end
128
+
129
+ def new_line
130
+ @output << @break if !@anchored
131
+ @anchored = true
132
+ end
133
+
134
+ def step_in(new_state)
135
+ @state = new_state
136
+ @depth += 1
137
+ update_layout
138
+ end
139
+
140
+ def step_out(new_state)
141
+ @state = new_state
142
+ @depth -=1
143
+ update_layout
144
+ end
145
+
146
+ def update_layout
147
+ if @pretty
148
+ @break = "\n"
149
+ @indent = ""
150
+ else
151
+ @break = " "
152
+ @indent = ""
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
5
+ # To use the flat printer:
6
+ #
7
+ # result = RubyProf.profile do
8
+ # [code to profile]
9
+ # end
10
+ #
11
+ # printer = RubyProf::FlatPrinter.new(result)
12
+ # printer.print(STDOUT, {})
13
+ #
14
+ class FlatPrinter < AbstractPrinter
15
+ # Override for this printer to sort by self time by default
16
+ def sort_method
17
+ @options[:sort_method] || :self_time
18
+ end
19
+
20
+ private
21
+
22
+ #def print_threads
23
+ # @result.threads.each do |thread|
24
+ # print_thread(thread)
25
+ # @output << "\n" * 2
26
+ # end
27
+ #end
28
+
29
+ def print_header(thread)
30
+ @output << "Measure Mode: %s\n" % RubyProf.measure_mode_string
31
+ @output << "Thread ID: %d\n" % thread.id
32
+ @output << "Fiber ID: %d\n" % thread.fiber_id unless thread.id == thread.fiber_id
33
+ @output << "Total: %0.6f\n" % thread.total_time
34
+ @output << "Sort by: #{sort_method}\n"
35
+ @output << "\n"
36
+ @output << " %self total self wait child calls name\n"
37
+ end
38
+
39
+ def print_methods(thread)
40
+ total_time = thread.total_time
41
+ methods = thread.methods.sort_by(&sort_method).reverse
42
+
43
+ sum = 0
44
+ methods.each do |method|
45
+ self_percent = (method.self_time / total_time) * 100
46
+ next if self_percent < min_percent
47
+
48
+ sum += method.self_time
49
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
50
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
51
+
52
+ @output << "%6.2f %9.3f %9.3f %9.3f %9.3f %8d %s%s\n" % [
53
+ method.self_time / total_time * 100, # %self
54
+ method.total_time, # total
55
+ method.self_time, # self
56
+ method.wait_time, # wait
57
+ method.children_time, # children
58
+ method.called, # calls
59
+ method.recursive? ? "*" : " ", # cycle
60
+ method_name(method) # name
61
+ ]
62
+ end
63
+ end
64
+
65
+ def print_footer(thread)
66
+ @output << "\n"
67
+ @output << "* indicates recursively called methods\n"
68
+ end
69
+ end
70
+ end