ruby-prof 0.18.0-x64-mingw32 → 1.1.0-x64-mingw32

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +32 -0
  3. data/LICENSE +2 -2
  4. data/README.rdoc +1 -483
  5. data/Rakefile +3 -6
  6. data/bin/ruby-prof +65 -30
  7. data/ext/ruby_prof/extconf.rb +6 -38
  8. data/ext/ruby_prof/rp_allocation.c +279 -0
  9. data/ext/ruby_prof/rp_allocation.h +31 -0
  10. data/ext/ruby_prof/rp_call_info.c +129 -283
  11. data/ext/ruby_prof/rp_call_info.h +16 -34
  12. data/ext/ruby_prof/rp_measure_allocations.c +25 -49
  13. data/ext/ruby_prof/rp_measure_memory.c +21 -56
  14. data/ext/ruby_prof/rp_measure_process_time.c +35 -39
  15. data/ext/ruby_prof/rp_measure_wall_time.c +36 -19
  16. data/ext/ruby_prof/rp_measurement.c +230 -0
  17. data/ext/ruby_prof/rp_measurement.h +50 -0
  18. data/ext/ruby_prof/rp_method.c +389 -389
  19. data/ext/ruby_prof/rp_method.h +34 -39
  20. data/ext/ruby_prof/rp_profile.c +895 -0
  21. data/ext/ruby_prof/rp_profile.h +37 -0
  22. data/ext/ruby_prof/rp_stack.c +103 -80
  23. data/ext/ruby_prof/rp_stack.h +5 -12
  24. data/ext/ruby_prof/rp_thread.c +143 -83
  25. data/ext/ruby_prof/rp_thread.h +15 -6
  26. data/ext/ruby_prof/ruby_prof.c +11 -757
  27. data/ext/ruby_prof/ruby_prof.h +4 -47
  28. data/ext/ruby_prof/vc/ruby_prof.vcxproj +10 -8
  29. data/lib/{2.6.3 → 2.6.5}/ruby_prof.so +0 -0
  30. data/lib/ruby-prof.rb +2 -18
  31. data/lib/ruby-prof/assets/call_stack_printer.html.erb +713 -0
  32. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  33. data/lib/ruby-prof/assets/graph_printer.html.erb +356 -0
  34. data/lib/ruby-prof/call_info.rb +35 -93
  35. data/lib/ruby-prof/call_info_visitor.rb +19 -21
  36. data/lib/ruby-prof/compatibility.rb +37 -107
  37. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  38. data/lib/ruby-prof/measurement.rb +14 -0
  39. data/lib/ruby-prof/method_info.rb +52 -83
  40. data/lib/ruby-prof/printers/abstract_printer.rb +73 -50
  41. data/lib/ruby-prof/printers/call_info_printer.rb +13 -3
  42. data/lib/ruby-prof/printers/call_stack_printer.rb +62 -145
  43. data/lib/ruby-prof/printers/call_tree_printer.rb +20 -12
  44. data/lib/ruby-prof/printers/dot_printer.rb +5 -5
  45. data/lib/ruby-prof/printers/flat_printer.rb +6 -24
  46. data/lib/ruby-prof/printers/graph_html_printer.rb +6 -192
  47. data/lib/ruby-prof/printers/graph_printer.rb +13 -15
  48. data/lib/ruby-prof/printers/multi_printer.rb +66 -23
  49. data/lib/ruby-prof/profile.rb +10 -3
  50. data/lib/ruby-prof/rack.rb +0 -3
  51. data/lib/ruby-prof/thread.rb +12 -12
  52. data/lib/ruby-prof/version.rb +1 -1
  53. data/ruby-prof.gemspec +2 -2
  54. data/test/abstract_printer_test.rb +0 -27
  55. data/test/alias_test.rb +129 -0
  56. data/test/basic_test.rb +41 -40
  57. data/test/call_info_visitor_test.rb +3 -3
  58. data/test/dynamic_method_test.rb +0 -2
  59. data/test/fiber_test.rb +11 -17
  60. data/test/gc_test.rb +96 -0
  61. data/test/line_number_test.rb +120 -39
  62. data/test/marshal_test.rb +119 -0
  63. data/test/measure_allocations.rb +30 -0
  64. data/test/measure_allocations_test.rb +371 -12
  65. data/test/measure_allocations_trace_test.rb +385 -0
  66. data/test/measure_memory_trace_test.rb +756 -0
  67. data/test/measure_process_time_test.rb +821 -33
  68. data/test/measure_times.rb +54 -0
  69. data/test/measure_wall_time_test.rb +349 -145
  70. data/test/multi_printer_test.rb +1 -34
  71. data/test/parser_timings.rb +24 -0
  72. data/test/pause_resume_test.rb +5 -5
  73. data/test/prime.rb +2 -0
  74. data/test/printer_call_stack_test.rb +28 -0
  75. data/test/printer_call_tree_test.rb +31 -0
  76. data/test/printer_flat_test.rb +68 -0
  77. data/test/printer_graph_html_test.rb +60 -0
  78. data/test/printer_graph_test.rb +41 -0
  79. data/test/printers_test.rb +32 -166
  80. data/test/printing_recursive_graph_test.rb +26 -72
  81. data/test/recursive_test.rb +72 -77
  82. data/test/stack_printer_test.rb +2 -15
  83. data/test/start_stop_test.rb +22 -25
  84. data/test/test_helper.rb +5 -248
  85. data/test/thread_test.rb +11 -54
  86. data/test/unique_call_path_test.rb +16 -28
  87. data/test/yarv_test.rb +1 -0
  88. metadata +28 -36
  89. data/examples/flat.txt +0 -50
  90. data/examples/graph.dot +0 -84
  91. data/examples/graph.html +0 -823
  92. data/examples/graph.txt +0 -139
  93. data/examples/multi.flat.txt +0 -23
  94. data/examples/multi.graph.html +0 -760
  95. data/examples/multi.grind.dat +0 -114
  96. data/examples/multi.stack.html +0 -547
  97. data/examples/stack.html +0 -547
  98. data/ext/ruby_prof/rp_measure.c +0 -40
  99. data/ext/ruby_prof/rp_measure.h +0 -45
  100. data/ext/ruby_prof/rp_measure_cpu_time.c +0 -136
  101. data/ext/ruby_prof/rp_measure_gc_runs.c +0 -73
  102. data/ext/ruby_prof/rp_measure_gc_time.c +0 -60
  103. data/lib/ruby-prof/aggregate_call_info.rb +0 -76
  104. data/lib/ruby-prof/assets/call_stack_printer.css.html +0 -117
  105. data/lib/ruby-prof/assets/call_stack_printer.js.html +0 -385
  106. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +0 -83
  107. data/lib/ruby-prof/profile/exclude_common_methods.rb +0 -207
  108. data/lib/ruby-prof/profile/legacy_method_elimination.rb +0 -50
  109. data/test/aggregate_test.rb +0 -136
  110. data/test/block_test.rb +0 -74
  111. data/test/call_info_test.rb +0 -78
  112. data/test/issue137_test.rb +0 -63
  113. data/test/measure_cpu_time_test.rb +0 -212
  114. data/test/measure_gc_runs_test.rb +0 -32
  115. data/test/measure_gc_time_test.rb +0 -36
  116. data/test/measure_memory_test.rb +0 -33
  117. data/test/method_elimination_test.rb +0 -84
  118. data/test/module_test.rb +0 -45
  119. data/test/stack_test.rb +0 -138
@@ -0,0 +1,14 @@
1
+ module RubyProf
2
+ # The Measurement class is a helper class used by RubyProf::MethodInfo to store information about the method.
3
+ # You cannot create a CallInfo object directly, they are generated while running a profile.
4
+ class Measurement
5
+ # :nodoc:
6
+ def to_s
7
+ "c: #{called}, tt: #{total_time}, st: #{self_time}"
8
+ end
9
+
10
+ def inspect
11
+ super + "(#{self.to_s})"
12
+ end
13
+ end
14
+ end
@@ -1,108 +1,78 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module RubyProf
4
+ # The MethodInfo class is used to track information about each method that is profiled.
5
+ # You cannot create a MethodInfo object directly, they are generated while running a profile.
4
6
  class MethodInfo
5
7
  include Comparable
6
8
 
7
- def <=>(other)
8
- if self.total_time < other.total_time
9
- -1
10
- elsif self.total_time > other.total_time
11
- 1
12
- elsif self.min_depth < other.min_depth
13
- 1
14
- elsif self.min_depth > other.min_depth
15
- -1
16
- else
17
- self.full_name <=> other.full_name
18
- end
9
+ # Returns the full name of a class. The interpretation of method names is:
10
+ #
11
+ # * MyObject#test - An method defined in a class
12
+ # * <Class:MyObject>#test - A method defined in a singleton class.
13
+ # * <Module:MyObject>#test - A method defined in a singleton module.
14
+ # * <Object:MyObject>#test - A method defined in a singleton object.
15
+ def full_name
16
+ decorated_class_name = case self.klass_flags
17
+ when 0x2
18
+ "<Class::#{klass_name}>"
19
+ when 0x4
20
+ "<Module::#{klass_name}>"
21
+ when 0x8
22
+ "<Object::#{klass_name}>"
23
+ else
24
+ klass_name
25
+ end
26
+
27
+ "#{decorated_class_name}##{method_name}"
19
28
  end
20
29
 
30
+ # The number of times this method was called
21
31
  def called
22
- @called ||= begin
23
- call_infos.inject(0) do |sum, call_info|
24
- sum + call_info.called
25
- end
26
- end
32
+ self.measurement.called
27
33
  end
28
34
 
35
+ # The total time this method took - includes self time + wait time + child time
29
36
  def total_time
30
- @total_time ||= begin
31
- call_infos.inject(0) do |sum, call_info|
32
- sum += call_info.total_time if !call_info.recursive?
33
- sum
34
- end
35
- end
37
+ self.measurement.total_time
36
38
  end
37
39
 
40
+ # The time this method took to execute
38
41
  def self_time
39
- @self_time ||= begin
40
- call_infos.inject(0) do |sum, call_info|
41
- sum += call_info.self_time if !call_info.recursive?
42
- sum
43
- end
44
- end
42
+ self.measurement.self_time
45
43
  end
46
44
 
45
+ # The time this method waited for other fibers/threads to execute
47
46
  def wait_time
48
- @wait_time ||= begin
49
- call_infos.inject(0) do |sum, call_info|
50
- sum += call_info.wait_time if !call_info.recursive?
51
- sum
52
- end
53
- end
47
+ self.measurement.wait_time
54
48
  end
55
49
 
50
+ # The time this method's children took to execute
56
51
  def children_time
57
- @children_time ||= begin
58
- call_infos.inject(0) do |sum, call_info|
59
- sum += call_info.children_time if !call_info.recursive?
60
- sum
61
- end
62
- end
52
+ self.total_time - self.self_time - self.wait_time
63
53
  end
64
54
 
55
+ # The min call depth of this method
65
56
  def min_depth
66
- @min_depth ||= call_infos.map(&:depth).min
67
- end
68
-
69
- def root?
70
- @root ||= begin
71
- call_infos.find do |call_info|
72
- not call_info.root?
73
- end.nil?
74
- end
75
- end
76
-
77
- def children
78
- @children ||= call_infos.map(&:children).flatten
57
+ @min_depth ||= callers.map(&:depth).min
79
58
  end
80
59
 
81
- def parents
82
- @parents ||= call_infos.map(&:parent)
83
- end
84
-
85
- def aggregate_parents
86
- # group call infos based on their parents
87
- groups = self.call_infos.each_with_object({}) do |call_info, hash|
88
- key = call_info.parent ? call_info.parent.target : self
89
- (hash[key] ||= []) << call_info
90
- end
91
-
92
- groups.map do |key, value|
93
- AggregateCallInfo.new(value, self)
94
- end
95
- end
96
-
97
- def aggregate_children
98
- # group call infos based on their targets
99
- groups = self.children.each_with_object({}) do |call_info, hash|
100
- key = call_info.target
101
- (hash[key] ||= []) << call_info
102
- end
103
-
104
- groups.map do |key, value|
105
- AggregateCallInfo.new(value, self)
60
+ # :enddoc:
61
+ def <=>(other)
62
+ if other == nil
63
+ -1
64
+ elsif self.full_name == other.full_name
65
+ 0
66
+ elsif self.total_time < other.total_time
67
+ -1
68
+ elsif self.total_time > other.total_time
69
+ 1
70
+ elsif self.min_depth < other.min_depth
71
+ 1
72
+ elsif self.min_depth > other.min_depth
73
+ -1
74
+ else
75
+ self.full_name <=> other.full_name
106
76
  end
107
77
  end
108
78
 
@@ -110,12 +80,11 @@ module RubyProf
110
80
  "#{self.full_name} (c: #{self.called}, tt: #{self.total_time}, st: #{self.self_time}, wt: #{wait_time}, ct: #{self.children_time})"
111
81
  end
112
82
 
113
- # remove method from the call graph. should not be called directly.
83
+ # Remove method from the call graph. should not be called directly.
114
84
  def eliminate!
115
85
  # $stderr.puts "eliminating #{self}"
116
- call_infos.each{ |call_info| call_info.eliminate! }
117
- call_infos.clear
86
+ callers.each{ |call_info| call_info.eliminate! }
87
+ callers.clear
118
88
  end
119
-
120
89
  end
121
90
  end
@@ -1,7 +1,14 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module RubyProf
4
+ # This is the base class for all Printers. It is never used directly.
4
5
  class AbstractPrinter
6
+ # :stopdoc:
7
+ def self.needs_dir?
8
+ false
9
+ end
10
+ # :startdoc:
11
+
5
12
  # Create a new printer.
6
13
  #
7
14
  # result should be the output generated from a profiling run
@@ -10,72 +17,63 @@ module RubyProf
10
17
  @output = nil
11
18
  end
12
19
 
13
- # Specify print options.
20
+ # Returns the min_percent of total time a method must take to be included in a profiling report
21
+ def min_percent
22
+ @options[:min_percent] || 0
23
+ end
24
+
25
+ # Returns the time format used to show when a profile was run
26
+ def time_format
27
+ '%A, %B %-d at %l:%M:%S %p (%Z)'
28
+ end
29
+
30
+ # Returns how profile data should be sorted
31
+ def sort_method
32
+ @options[:sort_method]
33
+ end
34
+
35
+ # Prints a report to the provided output.
36
+ #
37
+ # output - Any IO object, including STDOUT or a file.
38
+ # The default value is STDOUT.
39
+ #
40
+ # options - Hash of print options. Note that each printer can
41
+ # define its own set of options.
14
42
  #
15
- # options - Hash table
16
43
  # :min_percent - Number 0 to 100 that specifes the minimum
17
44
  # %self (the methods self time divided by the
18
45
  # overall total time) that a method must take
19
46
  # for it to be printed out in the report.
20
47
  # Default value is 0.
21
48
  #
22
- # :print_file - True or false. Specifies if a method's source
23
- # file should be printed. Default value if false.
24
- #
25
49
  # :sort_method - Specifies method used for sorting method infos.
26
50
  # Available values are :total_time, :self_time,
27
51
  # :wait_time, :children_time
28
52
  # Default value is :total_time
29
- # :editor_uri - Specifies editor uri scheme used for opening files
30
- # e.g. :atm or :mvim. For OS X default is :txmt.
31
- # Pass false to print bare filenames.
32
- # Use RUBY_PROF_EDITOR_URI environment variable to override.
33
- def setup_options(options = {})
34
- @options = options
35
- end
36
-
37
- def min_percent
38
- @options[:min_percent] || 0
39
- end
40
-
41
- def print_file
42
- @options[:print_file] || false
53
+ def print(output = STDOUT, options = {})
54
+ @output = output
55
+ setup_options(options)
56
+ print_threads
43
57
  end
44
58
 
45
- def sort_method
46
- @options[:sort_method] || :total_time
59
+ # :nodoc:
60
+ def setup_options(options = {})
61
+ @options = options
47
62
  end
48
63
 
49
- def editor_uri
50
- if ENV.key?('RUBY_PROF_EDITOR_URI')
51
- ENV['RUBY_PROF_EDITOR_URI'] || false
52
- elsif @options.key?(:editor_uri)
53
- @options[:editor_uri]
54
- else
55
- RUBY_PLATFORM =~ /darwin/ ? 'txmt' : false
64
+ def method_location(method)
65
+ if method.source_file
66
+ "#{method.source_file}:#{method.line}"
56
67
  end
57
68
  end
58
69
 
59
- def method_name(method)
60
- name = method.full_name
61
- if print_file
62
- name += " (#{method.source_file}:#{method.line}}"
63
- end
64
- name
70
+ def method_href(thread, method)
71
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread.fiber_id.to_s)
65
72
  end
66
73
 
67
- # Print a profiling report to the provided output.
68
- #
69
- # output - Any IO object, including STDOUT or a file.
70
- # The default value is STDOUT.
71
- #
72
- # options - Hash of print options. See #setup_options
73
- # for more information. Note that each printer can
74
- # define its own set of options.
75
- def print(output = STDOUT, options = {})
76
- @output = output
77
- setup_options(options)
78
- print_threads
74
+ def open_asset(file)
75
+ path = File.join(File.expand_path('../../assets', __FILE__), file)
76
+ File.open(path, 'rb').read
79
77
  end
80
78
 
81
79
  def print_threads
@@ -91,14 +89,39 @@ module RubyProf
91
89
  end
92
90
 
93
91
  def print_header(thread)
92
+ @output << "Measure Mode: %s\n" % RubyProf.measure_mode_string
93
+ @output << "Thread ID: %d\n" % thread.id
94
+ @output << "Fiber ID: %d\n" % thread.fiber_id unless thread.id == thread.fiber_id
95
+ @output << "Total: %0.6f\n" % thread.total_time
96
+ @output << "Sort by: #{sort_method}\n"
97
+ @output << "\n"
98
+ print_column_headers
94
99
  end
95
100
 
96
- def print_footer(thread)
101
+ def print_column_headers
97
102
  end
98
103
 
99
- # whether this printer need a :path option pointing to a directory
100
- def self.needs_dir?
101
- false
104
+ def print_footer(thread)
105
+ @output << <<~EOT
106
+
107
+ * recursively called methods
108
+
109
+ Columns are:
110
+
111
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
112
+ total - The time spent in this method and its children.
113
+ self - The time spent in this method.
114
+ wait - The amount of time this method waited for other threads.
115
+ child - The time spent in this method's children.
116
+ calls - The number of times this method was called.
117
+ name - The name of the method.
118
+ location - The location of the method.
119
+
120
+ The interpretation of method names is:
121
+
122
+ * MyObject#test - An instance method "test" of the class "MyObject"
123
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
124
+ EOT
102
125
  end
103
126
  end
104
127
  end
@@ -4,7 +4,15 @@ module RubyProf
4
4
  # Prints out the call graph based on CallInfo instances. This
5
5
  # is mainly for debugging purposes as it provides access into
6
6
  # into RubyProf's internals.
7
-
7
+ #
8
+ # To use the printer:
9
+ #
10
+ # result = RubyProf.profile do
11
+ # [code to profile]
12
+ # end
13
+ #
14
+ # printer = RubyProf::CallInfoPrinter.new(result)
15
+ # printer.print(STDOUT)
8
16
  class CallInfoPrinter < AbstractPrinter
9
17
  TIME_WIDTH = 0
10
18
 
@@ -19,7 +27,7 @@ module RubyProf
19
27
  end
20
28
 
21
29
  def print_methods(thread)
22
- visitor = CallInfoVisitor.new(thread.top_call_infos)
30
+ visitor = CallInfoVisitor.new(thread.root_methods)
23
31
 
24
32
  visitor.visit do |call_info, event|
25
33
  if event == :enter
@@ -31,11 +39,13 @@ module RubyProf
31
39
  @output << "wt:#{sprintf("%#{TIME_WIDTH}.2f", call_info.wait_time)}, "
32
40
  @output << "ct:#{sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)}, "
33
41
  @output << "call:#{call_info.called}, "
34
- @output << "rec:#{call_info.recursive?}"
35
42
  @output << ")"
36
43
  @output << "\n"
37
44
  end
38
45
  end
39
46
  end
47
+
48
+ def print_footer(thread)
49
+ end
40
50
  end
41
51
  end
@@ -3,9 +3,20 @@
3
3
  require 'erb'
4
4
  require 'fileutils'
5
5
  require 'base64'
6
+ require 'set'
6
7
 
7
8
  module RubyProf
8
- # prints a HTML visualization of the call tree
9
+ # Prints a HTML visualization of the call tree.
10
+ #
11
+ # To use the printer:
12
+ #
13
+ # result = RubyProf.profile do
14
+ # [code to profile]
15
+ # end
16
+ #
17
+ # printer = RubyProf::CallStackPrinter.new(result)
18
+ # printer.print(STDOUT)
19
+
9
20
  class CallStackPrinter < AbstractPrinter
10
21
  include ERB::Util
11
22
 
@@ -18,9 +29,6 @@ module RubyProf
18
29
  # for it to be printed out in the report.
19
30
  # Default value is 0.
20
31
  #
21
- # :print_file - True or false. Specifies if a method's source
22
- # file should be printed. Default value if false.
23
- #
24
32
  # :threshold - a float from 0 to 100 that sets the threshold of
25
33
  # results displayed.
26
34
  # Default value is 1.0
@@ -35,78 +43,58 @@ module RubyProf
35
43
  #
36
44
  # :application - a String to overide the name of the application,
37
45
  # as it appears on the report.
38
- #
39
- # :editor_uri - Specifies editor uri scheme used for opening files
40
- # e.g. :atm or :mvim. For OS X default is :txmt.
41
- # Use RUBY_PROF_EDITOR_URI environment variable to overide.
42
46
  def print(output = STDOUT, options = {})
43
- @output = output
44
47
  setup_options(options)
45
- @editor = editor_uri
46
- if @graph_html = options.delete(:graph)
47
- @graph_html = "file://" + @graph_html if @graph_html[0]=="/"
48
- end
49
-
50
- print_header
51
-
52
- @overall_threads_time = @result.threads.inject(0) do |val, thread|
53
- val += thread.total_time
54
- end
55
-
56
- @result.threads.each do |thread|
57
- @current_thread_id = thread.fiber_id
58
- @overall_time = thread.total_time
59
- thread_info = String.new("Thread: #{thread.id}")
60
- thread_info << ", Fiber: #{thread.fiber_id}" unless thread.id == thread.fiber_id
61
- thread_info << " (#{"%4.2f%%" % ((@overall_time/@overall_threads_time)*100)} ~ #{@overall_time})"
62
- @output.print "<div class=\"thread\">#{thread_info}</div>"
63
- @output.print "<ul name=\"thread\">"
64
- thread.methods.each do |m|
65
- # $stderr.print m.dump
66
- next unless m.root?
67
- m.call_infos.each do |ci|
68
- next unless ci.root?
69
- print_stack ci, thread.total_time
70
- end
71
- end
72
- @output.print "</ul>"
73
- end
74
-
75
- print_footer
48
+ output << @erb.result(binding)
49
+ a = 1
50
+ end
76
51
 
52
+ # :enddoc:
53
+ def setup_options(options)
54
+ super(options)
55
+ @erb = ERB.new(self.template)
77
56
  end
78
57
 
79
- def print_stack(call_info, parent_time)
58
+ def print_stack(output, visited, call_info, parent_time)
80
59
  total_time = call_info.total_time
81
60
  percent_parent = (total_time/parent_time)*100
82
61
  percent_total = (total_time/@overall_time)*100
83
62
  return unless percent_total > min_percent
84
63
  color = self.color(percent_total)
85
- kids = call_info.children
64
+ kids = call_info.target.callees
86
65
  visible = percent_total >= threshold
87
66
  expanded = percent_total >= expansion
88
67
  display = visible ? "block" : "none"
89
- @output.print "<li class=\"color#{color}\" style=\"display:#{display}\">"
90
- if kids.empty?
91
- @output.print "<a href=\"#\" class=\"toggle empty\" ></a>"
68
+
69
+ output << "<li class=\"color#{color}\" style=\"display:#{display}\">" << "\n"
70
+
71
+ if visited.include?(call_info)
72
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
73
+ output << "<span>%s %s</span>" % [link(call_info.target, true), graph_link(call_info)] << "\n"
92
74
  else
93
- visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
94
- image = visible_children ? (expanded ? "minus" : "plus") : "empty"
95
- @output.print "<a href=\"#\" class=\"toggle #{image}\" ></a>"
96
- end
97
- @output.printf "<span> %4.2f%% (%4.2f%%) %s %s</span>\n", percent_total, percent_parent, link(call_info), graph_link(call_info)
98
- unless kids.empty?
99
- if expanded
100
- @output.print "<ul>"
75
+ visited << call_info
76
+
77
+ if kids.empty?
78
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
101
79
  else
102
- @output.print '<ul style="display:none">'
80
+ visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
81
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
82
+ output << "<a href=\"#\" class=\"toggle #{image}\" ></a>" << "\n"
103
83
  end
104
- kids.sort_by{|c| -c.total_time}.each do |callinfo|
105
- print_stack callinfo, total_time
84
+ output << "<span>%4.2f%% (%4.2f%%) %s %s</span>" % [percent_total, percent_parent,
85
+ link(call_info.target, false), graph_link(call_info)] << "\n"
86
+
87
+ unless kids.empty?
88
+ output << (expanded ? '<ul>' : '<ul style="display:none">') << "\n"
89
+ kids.sort_by{|c| -c.total_time}.each do |child_call_info|
90
+ print_stack(output, visited, child_call_info, total_time)
91
+ end
92
+ output << '</ul>' << "\n"
106
93
  end
107
- @output.print "</ul>"
94
+
95
+ visited.delete(call_info)
108
96
  end
109
- @output.print "</li>"
97
+ output << '</li>' << "\n"
110
98
  end
111
99
 
112
100
  def name(call_info)
@@ -114,31 +102,25 @@ module RubyProf
114
102
  method.full_name
115
103
  end
116
104
 
117
- def link(call_info)
118
- method = call_info.target
119
- file = File.expand_path(method.source_file)
120
- if file =~ /\/ruby_runtime$/
121
- h(name(call_info))
105
+ def link(method, recursive)
106
+ method_name = "#{recursive ? '*' : ''}#{method.full_name}"
107
+ if method.source_file.nil?
108
+ h method_name
122
109
  else
123
- if @editor
124
- "<a href=\"#{@editor}://" \
125
- "open?url=file://#{file}&line=#{method.line}\">" \
126
- "#{h(name(call_info))}</a>"
127
- else
128
- "<a href=\"file://#{file}##{method.line}\">#{h(name(call_info))}</a>"
129
- end
110
+ file = File.expand_path(method.source_file)
111
+ "<a href=\"file://#{file}##{method.line}\">#{h method_name}</a>"
130
112
  end
131
113
  end
132
114
 
133
115
  def graph_link(call_info)
134
- total_calls = call_info.target.call_infos.inject(0){|t, ci| t += ci.called}
135
- href = "#{@graph_html}##{method_href(call_info.target)}"
136
- totals = @graph_html ? "<a href='#{href}'>#{total_calls}</a>" : total_calls.to_s
116
+ total_calls = call_info.target.called
117
+ href = "#{method_href(call_info.target)}"
118
+ totals = total_calls.to_s
137
119
  "[#{call_info.called} calls, #{totals} total]"
138
120
  end
139
121
 
140
122
  def method_href(method)
141
- h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
123
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_"))
142
124
  end
143
125
 
144
126
  def total_time(call_infos)
@@ -186,80 +168,15 @@ module RubyProf
186
168
  @options[:expansion] || 10.0
187
169
  end
188
170
 
189
- def print_header
190
- @output.puts "<html><head>"
191
- @output.puts '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
192
- @output.puts "<title>#{h title}</title>"
193
- print_css
194
- print_java_script
195
- @output.puts '</head><body><div style="display: inline-block;">'
196
- print_title_bar
197
- print_commands
198
- print_help
199
- end
200
-
201
- def print_footer
202
- @output.puts '<div id="sentinel"></div></div></body></html>'
203
- end
204
-
205
- def open_asset(file)
206
- path = File.join(File.expand_path('../../assets', __FILE__), file)
207
- File.open(path, 'rb').read
208
- end
209
-
210
- def print_css
211
- html = open_asset('call_stack_printer.css.html')
212
- @output.puts html.gsub('%s', base64_image)
213
- end
214
-
215
171
  def base64_image
216
- file = open_asset('call_stack_printer.png')
217
- Base64.encode64(file).gsub(/\n/, '')
218
- end
219
-
220
- def print_java_script
221
- html = open_asset('call_stack_printer.js.html')
222
- @output.puts html
223
- end
224
-
225
- def print_title_bar
226
- @output.puts <<-"end_title_bar"
227
- <div id="titlebar">
228
- Call tree for application <b>#{h application} #{h arguments}</b><br/>
229
- Generated on #{Time.now} with options #{h @options.inspect}<br/>
230
- </div>
231
- end_title_bar
232
- end
233
-
234
- def print_commands
235
- @output.puts <<-"end_commands"
236
- <div id=\"commands\">
237
- <span style=\"font-size: 11pt; font-weight: bold;\">Threshold:</span>
238
- <input value=\"#{h threshold}\" size=\"3\" id=\"threshold\" type=\"text\">
239
- <input value=\"Apply\" onclick=\"setThreshold();\" type=\"submit\">
240
- <input value=\"Expand All\" onclick=\"expandAll(event);\" type=\"submit\">
241
- <input value=\"Collapse All\" onclick=\"collapseAll(event);\" type=\"submit\">
242
- <input value=\"Show Help\" onclick=\"toggleHelp(this);\" type=\"submit\">
243
- </div>
244
- end_commands
172
+ @data ||= begin
173
+ file = open_asset('call_stack_printer.png')
174
+ Base64.encode64(file).gsub(/\n/, '')
175
+ end
245
176
  end
246
177
 
247
- def print_help
248
- @output.puts <<-'end_help'
249
- <div style="display: none;" id="help">
250
- &#8226; Enter a decimal value <i>d</i> into the threshold field and click "Apply"
251
- to hide all nodes marked with time values lower than <i>d</i>.<br>
252
- &#8226; Click on "Expand All" for full tree expansion.<br>
253
- &#8226; Click on "Collapse All" to show only top level nodes.<br>
254
- &#8226; Use a, s, d, w as in Quake or Urban Terror to navigate the tree.<br>
255
- &#8226; Use f and b to navigate the tree in preorder forward and backwards.<br>
256
- &#8226; Use x to toggle visibility of a subtree.<br>
257
- &#8226; Use * to expand/collapse a whole subtree.<br>
258
- &#8226; Use h to navigate to thread root.<br>
259
- &#8226; Use n and p to navigate between threads.<br>
260
- &#8226; Click on background to move focus to a subtree.<br>
261
- </div>
262
- end_help
178
+ def template
179
+ open_asset('call_stack_printer.html.erb')
263
180
  end
264
181
  end
265
182
  end