acunote-ruby-prof 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGES +240 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +439 -0
  4. data/Rakefile +148 -0
  5. data/bin/ruby-prof +236 -0
  6. data/examples/empty.png +0 -0
  7. data/examples/flat.txt +55 -0
  8. data/examples/graph.dot +106 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.png +0 -0
  11. data/examples/graph.txt +170 -0
  12. data/examples/minus.png +0 -0
  13. data/examples/multi.flat.txt +23 -0
  14. data/examples/multi.graph.html +906 -0
  15. data/examples/multi.grind.dat +194 -0
  16. data/examples/multi.stack.html +573 -0
  17. data/examples/plus.png +0 -0
  18. data/examples/stack.html +573 -0
  19. data/ext/ruby_prof/extconf.rb +43 -0
  20. data/ext/ruby_prof/measure_allocations.h +58 -0
  21. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  22. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  23. data/ext/ruby_prof/measure_gc_time.h +57 -0
  24. data/ext/ruby_prof/measure_memory.h +101 -0
  25. data/ext/ruby_prof/measure_process_time.h +52 -0
  26. data/ext/ruby_prof/measure_wall_time.h +53 -0
  27. data/ext/ruby_prof/mingw/Rakefile +23 -0
  28. data/ext/ruby_prof/mingw/build.rake +38 -0
  29. data/ext/ruby_prof/ruby_prof.c +1834 -0
  30. data/ext/ruby_prof/ruby_prof.h +190 -0
  31. data/ext/ruby_prof/version.h +4 -0
  32. data/lib/ruby-prof.rb +62 -0
  33. data/lib/ruby-prof/abstract_printer.rb +41 -0
  34. data/lib/ruby-prof/aggregate_call_info.rb +68 -0
  35. data/lib/ruby-prof/call_info.rb +112 -0
  36. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  37. data/lib/ruby-prof/call_tree_printer.rb +133 -0
  38. data/lib/ruby-prof/dot_printer.rb +153 -0
  39. data/lib/ruby-prof/empty.png +0 -0
  40. data/lib/ruby-prof/flat_printer.rb +78 -0
  41. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  42. data/lib/ruby-prof/graph_html_printer.rb +278 -0
  43. data/lib/ruby-prof/graph_printer.rb +245 -0
  44. data/lib/ruby-prof/method_info.rb +131 -0
  45. data/lib/ruby-prof/minus.png +0 -0
  46. data/lib/ruby-prof/multi_printer.rb +54 -0
  47. data/lib/ruby-prof/plus.png +0 -0
  48. data/lib/ruby-prof/rack.rb +30 -0
  49. data/lib/ruby-prof/result.rb +70 -0
  50. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  51. data/lib/ruby-prof/task.rb +146 -0
  52. data/lib/ruby-prof/test.rb +148 -0
  53. data/lib/unprof.rb +8 -0
  54. data/rails/environment/profile.rb +24 -0
  55. data/rails/example/example_test.rb +9 -0
  56. data/rails/profile_test_helper.rb +21 -0
  57. data/test/aggregate_test.rb +136 -0
  58. data/test/basic_test.rb +290 -0
  59. data/test/current_failures_windows +8 -0
  60. data/test/do_nothing.rb +0 -0
  61. data/test/duplicate_names_test.rb +32 -0
  62. data/test/enumerable_test.rb +16 -0
  63. data/test/exceptions_test.rb +15 -0
  64. data/test/exclude_threads_test.rb +54 -0
  65. data/test/exec_test.rb +14 -0
  66. data/test/line_number_test.rb +73 -0
  67. data/test/measurement_test.rb +122 -0
  68. data/test/method_elimination_test.rb +74 -0
  69. data/test/module_test.rb +44 -0
  70. data/test/multi_printer_test.rb +81 -0
  71. data/test/no_method_class_test.rb +13 -0
  72. data/test/prime.rb +55 -0
  73. data/test/prime_test.rb +13 -0
  74. data/test/printers_test.rb +164 -0
  75. data/test/recursive_test.rb +236 -0
  76. data/test/ruby-prof-bin +20 -0
  77. data/test/singleton_test.rb +38 -0
  78. data/test/stack_printer_test.rb +74 -0
  79. data/test/stack_test.rb +138 -0
  80. data/test/start_stop_test.rb +112 -0
  81. data/test/test_suite.rb +32 -0
  82. data/test/thread_test.rb +173 -0
  83. data/test/unique_call_path_test.rb +225 -0
  84. metadata +185 -0
@@ -0,0 +1,133 @@
1
+ require 'ruby-prof/abstract_printer'
2
+
3
+ module RubyProf
4
+ # Generate profiling information in calltree format
5
+ # for use by kcachegrind and similar tools.
6
+
7
+ class CallTreePrinter < AbstractPrinter
8
+ def print(output = STDOUT, options = {})
9
+ @output = output
10
+ setup_options(options)
11
+
12
+ # add a header - this information is somewhat arbitrary
13
+ @output << "events: "
14
+ case RubyProf.measure_mode
15
+ when RubyProf::PROCESS_TIME
16
+ @value_scale = RubyProf::CLOCKS_PER_SEC;
17
+ @output << 'process_time'
18
+ when RubyProf::WALL_TIME
19
+ @value_scale = 1_000_000
20
+ @output << 'wall_time'
21
+ when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
22
+ @value_scale = RubyProf.cpu_frequency
23
+ @output << 'cpu_time'
24
+ when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
25
+ @value_scale = 1
26
+ @output << 'allocations'
27
+ when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
28
+ @value_scale = 1
29
+ @output << 'memory'
30
+ when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
31
+ @value_scale = 1
32
+ @output << 'gc_runs'
33
+ when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
34
+ @value_scale = 1000000
35
+ @output << 'gc_time'
36
+ else
37
+ raise "Unknown measure mode: #{RubyProf.measure_mode}"
38
+ end
39
+ @output << "\n\n"
40
+
41
+ print_threads
42
+ end
43
+
44
+ def print_threads
45
+ @result.threads.each do |thread_id, methods|
46
+ print_methods(thread_id, methods)
47
+ end
48
+ end
49
+
50
+ def convert(value)
51
+ (value * @value_scale).round
52
+ end
53
+
54
+ def file(method)
55
+ File.expand_path(method.source_file)
56
+ end
57
+
58
+ def print_methods(thread_id, methods)
59
+ methods.reverse_each do |method|
60
+ # Print out the file and method name
61
+ @output << "fl=#{file(method)}\n"
62
+ @output << "fn=#{method_name(method)}\n"
63
+
64
+ # Now print out the function line number and its self time
65
+ @output << "#{method.line} #{convert(method.self_time)}\n"
66
+
67
+ # Now print out all the children methods
68
+ method.children.each do |callee|
69
+ @output << "cfl=#{file(callee.target)}\n"
70
+ @output << "cfn=#{method_name(callee.target)}\n"
71
+ @output << "calls=#{callee.called} #{callee.line}\n"
72
+
73
+ # Print out total times here!
74
+ @output << "#{callee.line} #{convert(callee.total_time)}\n"
75
+ end
76
+ @output << "\n"
77
+ end
78
+ end #end print_methods
79
+ end # end class
80
+
81
+ class CallTreePrinterWithoutAggregation < CallTreePrinter
82
+ def get_sequence_index(sequence)
83
+ @seq_cache ||= Hash.new
84
+ @seq_index ||= 1
85
+ sequence_array = sequence.split("->")
86
+ method_name = sequence_array.last
87
+ sequence = sequence_array.count>1 ? sequence_array[0..sequence_array.count - 2].join("->") : "root"
88
+ @seq_cache[method_name] = Hash.new unless @seq_cache.has_key?(method_name)
89
+ @seq_cache[method_name][sequence] = @seq_cache[method_name].keys.count+1 unless @seq_cache[method_name].include?(sequence)
90
+ @seq_cache[method_name][sequence]
91
+ end
92
+
93
+ def print_methods(thread_id, methods)
94
+ methods.reverse_each do |method|
95
+ parents = method.call_infos.map{|caller| caller if caller.parent}
96
+ children = method.children
97
+ call_tree = Hash.new
98
+ parents.each do |parent|
99
+ call_tree[parent] = Array.new
100
+ if parent
101
+ children.each do |child|
102
+ call_tree[parent] << child if child.call_sequence.include?(parent.call_sequence)
103
+ end
104
+ else
105
+ call_tree[parent] = children
106
+ end
107
+ end
108
+
109
+ call_tree.each do |parent, children|
110
+ # Print out the file and method name
111
+ @output << "fl=#{file(method)}\n"
112
+ @output << (parent ? "fn=#{method.full_name}(#{get_sequence_index(parent.call_sequence)})\n" : "fn=#{method.full_name}\n")
113
+
114
+ # Now print out the function line number and its self time
115
+ @output << "#{method.line} #{convert(method.self_time)}\n"
116
+
117
+ # Now print out all the children methods
118
+ children.each do |callee|
119
+ @output << "cfl=#{file(callee.target)}\n"
120
+ @output << "cfn=#{callee.target.full_name}(#{get_sequence_index(callee.call_sequence)})\n"
121
+ @output << "calls=#{callee.called} #{callee.line}\n"
122
+
123
+ # Print out total times here!
124
+ @output << "#{callee.line} #{convert(callee.total_time)}\n"
125
+ end
126
+ @output << "\n"
127
+ end
128
+ end
129
+ end #end print_methods
130
+
131
+ end #end class
132
+
133
+ end # end packages
@@ -0,0 +1,153 @@
1
+ require 'set'
2
+ require 'ruby-prof/abstract_printer'
3
+
4
+ module RubyProf
5
+ # Generates a graphviz graph in dot format.
6
+ # To use the dot printer:
7
+ #
8
+ # result = RubyProf.profile do
9
+ # [code to profile]
10
+ # end
11
+ #
12
+ # printer = RubyProf::DotPrinter.new(result)
13
+ # printer.print(STDOUT)
14
+ #
15
+ # You can use either dot viewer such as GraphViz, or the dot command line tool
16
+ # to reformat the output into a wide variety of outputs:
17
+ #
18
+ # dot -Tpng graph.dot > graph.png
19
+ #
20
+ class DotPrinter < RubyProf::AbstractPrinter
21
+ CLASS_COLOR = '"#666666"'
22
+ EDGE_COLOR = '"#666666"'
23
+
24
+ # Creates the DotPrinter using a RubyProf::Result.
25
+ def initialize(result)
26
+ super(result)
27
+ @seen_methods = Set.new
28
+ end
29
+
30
+ # Print a graph report to the provided output.
31
+ #
32
+ # output - Any IO object, including STDOUT or a file. The default value is
33
+ # STDOUT.
34
+ #
35
+ # options - Hash of print options. See #setup_options
36
+ # for more information.
37
+ #
38
+ # When profiling results that cover a large number of method calls it
39
+ # helps to use the :min_percent option, for example:
40
+ #
41
+ # DotPrinter.new(result).print(STDOUT, :min_percent=>5)
42
+ #
43
+ def print(output = STDOUT, options = {})
44
+ @output = output
45
+ setup_options(options)
46
+
47
+ total_time = thread_times.values.inject{|a,b| a+b}
48
+
49
+ puts 'digraph "Profile" {'
50
+ puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";"
51
+ puts "labelloc=t;"
52
+ puts "labeljust=l;"
53
+ print_threads
54
+ puts '}'
55
+ end
56
+
57
+ private
58
+
59
+ # Something of a hack, figure out which constant went with the
60
+ # RubyProf.measure_mode so that we can display it. Otherwise it's easy to
61
+ # forget what measurement was made.
62
+ def mode_name
63
+ mode = RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode}
64
+ end
65
+
66
+ # Computes the total time per thread:
67
+ def thread_times
68
+ @thread_times ||= begin
69
+ times = {}
70
+ @result.threads.each do |thread_id, methods|
71
+ toplevel = methods.sort.last
72
+
73
+ total_time = toplevel.total_time
74
+ # This looks like a hack for very small times... from GraphPrinter
75
+ total_time = 0.01 if total_time == 0
76
+ times[thread_id] = total_time
77
+ end
78
+
79
+ times
80
+ end
81
+ end
82
+
83
+ def print_threads
84
+ # sort assumes that spawned threads have higher object_ids
85
+ @result.threads.sort.each do |thread_id, methods|
86
+ puts "subgraph \"Thread #{thread_id}\" {"
87
+
88
+ print_methods(thread_id, methods)
89
+ puts "}"
90
+
91
+ print_classes(thread_id, methods)
92
+ end
93
+ end
94
+
95
+ # Determines an ID to use to represent the subject in the Dot file.
96
+ def dot_id(subject)
97
+ subject.object_id
98
+ end
99
+
100
+ def print_methods(thread_id, methods)
101
+ total_time = thread_times[thread_id]
102
+ # Print each method in total time order
103
+ methods.reverse_each do |method|
104
+ total_percentage = (method.total_time/total_time) * 100
105
+ self_percentage = (method.self_time/total_time) * 100
106
+
107
+ next if total_percentage < min_percent
108
+ name = method_name(method).split("#").last
109
+ puts "#{dot_id(method)} [label=\"#{name}\\n(#{total_percentage.round}%)\"];"
110
+ @seen_methods << method
111
+ print_edges(total_time, method)
112
+ end
113
+ end
114
+
115
+ def print_classes(thread_id, methods)
116
+ grouped = {}
117
+ methods.each{|m| grouped[m.klass_name] ||= []; grouped[m.klass_name] << m}
118
+ grouped.each do |cls, methods2|
119
+ # Filter down to just seen methods
120
+ big_methods, small_methods = methods2.partition{|m| @seen_methods.include? m}
121
+
122
+ if !big_methods.empty?
123
+ puts "subgraph cluster_#{cls.object_id} {"
124
+ puts "label = \"#{cls}\";"
125
+ puts "fontcolor = #{CLASS_COLOR};"
126
+ puts "fontsize = 16;"
127
+ puts "color = #{CLASS_COLOR};"
128
+ big_methods.each do |m|
129
+ puts "#{m.object_id};"
130
+ end
131
+ puts "}"
132
+ end
133
+ end
134
+ end
135
+
136
+ def print_edges(total_time, method)
137
+ method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
138
+
139
+ target_percentage = (child.target.total_time / total_time) * 100.0
140
+ next if target_percentage < min_percent
141
+
142
+ # Get children method
143
+ puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];"
144
+ end
145
+ end
146
+
147
+ # Silly little helper for printing to the @output
148
+ def puts(str)
149
+ @output.puts(str)
150
+ end
151
+
152
+ end
153
+ end
Binary file
@@ -0,0 +1,78 @@
1
+ require 'ruby-prof/abstract_printer'
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, 0)
13
+ #
14
+ class FlatPrinter < AbstractPrinter
15
+ # Print a flat profile report to the provided output.
16
+ #
17
+ # output - Any IO oject, including STDOUT or a file.
18
+ # The default value is STDOUT.
19
+ #
20
+ # options - Hash of print options. See #setup_options
21
+ # for more information.
22
+ #
23
+ def print(output = STDOUT, options = {})
24
+ @output = output
25
+ setup_options(options)
26
+ print_threads
27
+ end
28
+
29
+ private
30
+
31
+ def print_threads
32
+ @result.threads.each do |thread_id, methods|
33
+ print_methods(thread_id, methods)
34
+ @output << "\n" * 2
35
+ end
36
+ end
37
+
38
+ def print_methods(thread_id, methods)
39
+ # Get total time
40
+ toplevel = methods.max
41
+ total_time = toplevel.total_time
42
+ if total_time == 0
43
+ total_time = 0.01
44
+ end
45
+
46
+ # Now sort methods by largest self time,
47
+ # not total time like in other printouts
48
+ methods = methods.sort do |m1, m2|
49
+ m1.self_time <=> m2.self_time
50
+ end.reverse
51
+
52
+ @output << "Thread ID: %d\n" % thread_id
53
+ @output << "Total: %0.6f\n" % total_time
54
+ @output << "\n"
55
+ @output << " %self total self wait child calls name\n"
56
+
57
+ sum = 0
58
+ methods.each do |method|
59
+ self_percent = (method.self_time / total_time) * 100
60
+ next if self_percent < min_percent
61
+
62
+ sum += method.self_time
63
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
64
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
65
+
66
+ @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s\n" % [
67
+ method.self_time / total_time * 100, # %self
68
+ method.total_time, # total
69
+ method.self_time, # self
70
+ method.wait_time, # wait
71
+ method.children_time, # children
72
+ method.called, # calls
73
+ method_name(method) # name
74
+ ]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,72 @@
1
+ require 'ruby-prof/abstract_printer'
2
+ require 'pathname'
3
+ module RubyProf
4
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
5
+ # To use the flat printer with line numbers:
6
+ #
7
+ # result = RubyProf.profile do
8
+ # [code to profile]
9
+ # end
10
+ #
11
+ # printer = RubyProf::FlatPrinterWithLineNumbers.new(result)
12
+ # printer.print(STDOUT, 0)
13
+ #
14
+ class FlatPrinterWithLineNumbers < FlatPrinter
15
+
16
+ def print_methods(thread_id, methods)
17
+ # Get total time
18
+ toplevel = methods.max
19
+ total_time = toplevel.total_time
20
+ if total_time == 0
21
+ total_time = 0.01
22
+ end
23
+
24
+ # Now sort methods by largest self time,
25
+ # not total time like in other printouts
26
+ methods = methods.sort do |m1, m2|
27
+ m1.self_time <=> m2.self_time
28
+ end.reverse
29
+
30
+ @output << "Thread ID: %d\n" % thread_id
31
+ @output << "Total: %0.6f\n" % total_time
32
+ @output << "\n"
33
+ @output << " %self total self wait child calls name\n"
34
+ sum = 0
35
+ methods.each do |method|
36
+ self_percent = (method.self_time / total_time) * 100
37
+ next if self_percent < min_percent
38
+
39
+ sum += method.self_time
40
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
41
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
42
+
43
+ @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s " % [
44
+ method.self_time / total_time * 100, # %self
45
+ method.total_time, # total
46
+ method.self_time, # self
47
+ method.wait_time, # wait
48
+ method.children_time, # children
49
+ method.called, # calls
50
+ method_name(method), # name
51
+ ]
52
+ if method.source_file != 'ruby_runtime'
53
+ @output << " %s:%s" % [File.expand_path(method.source_file), method.line]
54
+ end
55
+ @output << "\n\tcalled from: "
56
+
57
+ # make sure they're unique
58
+ method.call_infos.map{|ci|
59
+ if ci.parent && ci.parent.target.source_file != 'ruby_runtime'
60
+ [method_name(ci.parent.target), File.expand_path(ci.parent.target.source_file), ci.parent.target.line]
61
+ else
62
+ nil
63
+ end
64
+ }.compact.uniq.each{|args|
65
+ @output << " %s (%s:%s) " % args
66
+ }
67
+ @output << "\n\n"
68
+ end
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,278 @@
1
+ require 'ruby-prof/abstract_printer'
2
+ require 'erb'
3
+
4
+ module RubyProf
5
+ # Generates graph[link:files/examples/graph_html.html] profile reports as html.
6
+ # To use the graph html printer:
7
+ #
8
+ # result = RubyProf.profile do
9
+ # [code to profile]
10
+ # end
11
+ #
12
+ # printer = RubyProf::GraphHtmlPrinter.new(result)
13
+ # printer.print(STDOUT, :min_percent=>0)
14
+ #
15
+ # The constructor takes two arguments. The first is
16
+ # a RubyProf::Result object generated from a profiling
17
+ # run. The second is the minimum %total (the methods
18
+ # total time divided by the overall total time) that
19
+ # a method must take for it to be printed out in
20
+ # the report. Use this parameter to eliminate methods
21
+ # that are not important to the overall profiling results.
22
+
23
+ class GraphHtmlPrinter < AbstractPrinter
24
+ include ERB::Util
25
+
26
+ PERCENTAGE_WIDTH = 8
27
+ TIME_WIDTH = 10
28
+ CALL_WIDTH = 20
29
+
30
+ # Create a GraphPrinter. Result is a RubyProf::Result
31
+ # object generated from a profiling run.
32
+ def initialize(result)
33
+ super(result)
34
+ @thread_times = Hash.new
35
+ calculate_thread_times
36
+ end
37
+
38
+ # Print a graph html report to the provided output.
39
+ #
40
+ # output - Any IO oject, including STDOUT or a file.
41
+ # The default value is STDOUT.
42
+ #
43
+ # options - Hash of print options. See #setup_options
44
+ # for more information.
45
+ #
46
+ def print(output = STDOUT, options = {})
47
+ @output = output
48
+ setup_options(options)
49
+
50
+ filename = options[:filename]
51
+ template = filename ? File.read(filename).untaint : (options[:template] || self.template)
52
+ _erbout = @output
53
+ erb = ERB.new(template, nil, nil)
54
+ erb.filename = filename
55
+ @output << erb.result(binding)
56
+ end
57
+
58
+ def total_time(call_infos)
59
+ sum(call_infos.map{|ci| ci.total_time})
60
+ end
61
+
62
+ def sum(a)
63
+ a.inject(0.0){|s,t| s+=t}
64
+ end
65
+
66
+ # These methods should be private but then ERB doesn't
67
+ # work. Turn off RDOC though
68
+ #--
69
+ def calculate_thread_times
70
+ # Cache thread times since this is an expensive
71
+ # operation with the required sorting
72
+ @overall_threads_time = 0.0
73
+ @thread_times = Hash.new
74
+ @result.threads.each do |thread_id, methods|
75
+ roots = methods.select{|m| m.root?}
76
+ thread_total_time = sum(roots.map{|r| self.total_time(r.call_infos)})
77
+ @overall_threads_time += thread_total_time
78
+ @thread_times[thread_id] = thread_total_time
79
+ end
80
+ end
81
+
82
+ def thread_time(thread_id)
83
+ @thread_times[thread_id]
84
+ end
85
+
86
+ def total_percent(thread_id, method)
87
+ overall_time = self.thread_time(thread_id)
88
+ (method.total_time/overall_time) * 100
89
+ end
90
+
91
+ def self_percent(method)
92
+ overall_time = self.thread_time(method.thread_id)
93
+ (method.self_time/overall_time) * 100
94
+ end
95
+
96
+ # Creates a link to a method. Note that we do not create
97
+ # links to methods which are under the min_perecent
98
+ # specified by the user, since they will not be
99
+ # printed out.
100
+ def create_link(thread_id, method)
101
+ if self.total_percent(thread_id, method) < min_percent
102
+ # Just return name
103
+ h method.full_name
104
+ else
105
+ href = '#' + method_href(thread_id, method)
106
+ "<a href=\"#{href}\">#{h method.full_name}</a>"
107
+ end
108
+ end
109
+
110
+ def method_href(thread_id, method)
111
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s)
112
+ end
113
+
114
+ def file_link(path, linenum)
115
+ srcfile = File.expand_path(path)
116
+ if srcfile =~ /\/ruby_runtime$/
117
+ ""
118
+ else
119
+ if RUBY_PLATFORM =~ /darwin/
120
+ "<a href=\"txmt://open?url=file://#{h srcfile}&line=#{linenum}\" title=\"#{h srcfile}:#{linenum}\">#{linenum}</a>"
121
+ else
122
+ "<a href=\"file://#{h srcfile}?line=#{linenum}\" title=\"#{h srcfile}:#{linenum}\">#{linenum}</a>"
123
+ end
124
+ end
125
+ end
126
+
127
+ def template
128
+ '
129
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
130
+ <html>
131
+ <head>
132
+ <style media="all" type="text/css">
133
+ table {
134
+ border-collapse: collapse;
135
+ border: 1px solid #CCC;
136
+ font-family: Verdana, Arial, Helvetica, sans-serif;
137
+ font-size: 9pt;
138
+ line-height: normal;
139
+ width: 100%;
140
+ }
141
+
142
+ th {
143
+ text-align: center;
144
+ border-top: 1px solid #FB7A31;
145
+ border-bottom: 1px solid #FB7A31;
146
+ background: #FFC;
147
+ padding: 0.3em;
148
+ border-left: 1px solid silver;
149
+ }
150
+
151
+ tr.break td {
152
+ border: 0;
153
+ border-top: 1px solid #FB7A31;
154
+ padding: 0;
155
+ margin: 0;
156
+ }
157
+
158
+ tr.method td {
159
+ font-weight: bold;
160
+ }
161
+
162
+ td {
163
+ padding: 0.3em;
164
+ }
165
+
166
+ td:first-child {
167
+ width: 190px;
168
+ }
169
+
170
+ td {
171
+ border-left: 1px solid #CCC;
172
+ text-align: center;
173
+ }
174
+
175
+ .method_name {
176
+ text-align: left;
177
+ }
178
+ </style>
179
+ </head>
180
+ <body>
181
+ <h1>Profile Report</h1>
182
+ <!-- Threads Table -->
183
+ <table>
184
+ <tr>
185
+ <th>Thread ID</th>
186
+ <th>Total Time</th>
187
+ </tr>
188
+ <% for thread_id in @result.threads.keys.sort %>
189
+ <tr>
190
+ <td><a href="#<%= thread_id %>"><%= thread_id %></a></td>
191
+ <td><%= thread_time(thread_id) %></td>
192
+ </tr>
193
+ <% end %>
194
+ </table>
195
+
196
+ <!-- Methods Tables -->
197
+ <% for thread_id in @result.threads.keys.sort
198
+ methods = @result.threads[thread_id]
199
+ total_time = thread_time(thread_id) %>
200
+ <h2><a name="<%= thread_id %>">Thread <%= thread_id %></a></h2>
201
+
202
+ <table>
203
+ <tr>
204
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Total") %></th>
205
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Self") %></th>
206
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Total") %></th>
207
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Self") %></th>
208
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Wait") %></th>
209
+ <th><%= sprintf("%#{TIME_WIDTH+2}s", "Child") %></th>
210
+ <th><%= sprintf("%#{CALL_WIDTH}s", "Calls") %></th>
211
+ <th class="method_name">Name</th>
212
+ <th>Line</th>
213
+ </tr>
214
+
215
+ <% min_time = @options[:min_time] || (@options[:nonzero] ? 0.005 : nil)
216
+ methods.sort.reverse_each do |method|
217
+ total_percentage = (method.total_time/total_time) * 100
218
+ next if total_percentage < min_percent
219
+ next if min_time && method.total_time < min_time
220
+ self_percentage = (method.self_time/total_time) * 100 %>
221
+
222
+ <!-- Parents -->
223
+ <% for caller in method.aggregate_parents.sort_by(&:total_time)
224
+ next unless caller.parent
225
+ next if min_time && caller.total_time < min_time %>
226
+ <tr>
227
+ <td>&nbsp;</td>
228
+ <td>&nbsp;</td>
229
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %></td>
230
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %></td>
231
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %></td>
232
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %></td>
233
+ <% called = "#{caller.called}/#{method.called}" %>
234
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
235
+ <td class="method_name"><%= create_link(thread_id, caller.parent.target) %></td>
236
+ <td><%= file_link(caller.parent.target.source_file, caller.line) %></td>
237
+ </tr>
238
+ <% end %>
239
+
240
+ <tr class="method">
241
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %></td>
242
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %></td>
243
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
244
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
245
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %></td>
246
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
247
+ <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
248
+ <td class="method_name"><a name="<%= method_href(thread_id, method) %>"><%= h method.full_name %></a></td>
249
+ <td><%= file_link(method.source_file, method.line) %></td>
250
+ </tr>
251
+
252
+ <!-- Children -->
253
+ <% for callee in method.aggregate_children.sort_by(&:total_time).reverse %>
254
+ <% next if min_time && callee.total_time < min_time %>
255
+ <tr>
256
+ <td>&nbsp;</td>
257
+ <td>&nbsp;</td>
258
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.total_time) %></td>
259
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.self_time) %></td>
260
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.wait_time) %></td>
261
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.children_time) %></td>
262
+ <% called = "#{callee.called}/#{callee.target.called}" %>
263
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
264
+ <td class="method_name"><%= create_link(thread_id, callee.target) %></td>
265
+ <td><%= file_link(method.source_file, callee.line) %></td>
266
+ </tr>
267
+ <% end %>
268
+ <!-- Create divider row -->
269
+ <tr class="break"><td colspan="9"></td></tr>
270
+ <% end %>
271
+ </table>
272
+ <% end %>
273
+ </body>
274
+ </html>'
275
+ end
276
+ end
277
+ end
278
+