ruby-prof 0.8.2 → 0.9.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 (74) hide show
  1. data/CHANGES +23 -13
  2. data/{README → README.rdoc} +118 -111
  3. data/Rakefile +14 -26
  4. data/bin/ruby-prof +16 -4
  5. data/examples/empty.png +0 -0
  6. data/examples/graph.dot +106 -0
  7. data/examples/graph.png +0 -0
  8. data/examples/minus.png +0 -0
  9. data/examples/multi.flat.txt +23 -0
  10. data/examples/multi.graph.html +906 -0
  11. data/examples/multi.grind.dat +194 -0
  12. data/examples/multi.stack.html +573 -0
  13. data/examples/plus.png +0 -0
  14. data/examples/stack.html +573 -0
  15. data/ext/ruby_prof/extconf.rb +0 -1
  16. data/ext/ruby_prof/measure_allocations.h +6 -6
  17. data/ext/ruby_prof/measure_cpu_time.h +5 -5
  18. data/ext/ruby_prof/measure_gc_runs.h +1 -1
  19. data/ext/ruby_prof/measure_gc_time.h +1 -1
  20. data/ext/ruby_prof/measure_memory.h +4 -4
  21. data/ext/ruby_prof/measure_process_time.h +1 -1
  22. data/ext/ruby_prof/measure_wall_time.h +1 -1
  23. data/ext/ruby_prof/ruby_prof.c +240 -167
  24. data/ext/ruby_prof/ruby_prof.h +12 -10
  25. data/ext/ruby_prof/version.h +3 -3
  26. data/lib/ruby-prof.rb +7 -1
  27. data/lib/ruby-prof/abstract_printer.rb +5 -5
  28. data/lib/ruby-prof/aggregate_call_info.rb +9 -3
  29. data/lib/ruby-prof/call_info.rb +66 -1
  30. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  31. data/lib/ruby-prof/call_tree_printer.rb +2 -2
  32. data/lib/ruby-prof/dot_printer.rb +151 -0
  33. data/lib/ruby-prof/empty.png +0 -0
  34. data/lib/ruby-prof/flat_printer.rb +16 -16
  35. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +13 -13
  36. data/lib/ruby-prof/graph_html_printer.rb +58 -36
  37. data/lib/ruby-prof/graph_printer.rb +30 -30
  38. data/lib/ruby-prof/method_info.rb +24 -4
  39. data/lib/ruby-prof/minus.png +0 -0
  40. data/lib/ruby-prof/multi_printer.rb +54 -0
  41. data/lib/ruby-prof/plus.png +0 -0
  42. data/lib/ruby-prof/rack.rb +28 -0
  43. data/lib/ruby-prof/result.rb +70 -0
  44. data/lib/ruby-prof/symbol_to_proc.rb +1 -1
  45. data/lib/ruby-prof/task.rb +19 -19
  46. data/lib/ruby-prof/test.rb +3 -3
  47. data/lib/ruby_prof.so +0 -0
  48. data/rails/environment/profile.rb +2 -2
  49. data/rails/example/example_test.rb +2 -2
  50. data/rails/profile_test_helper.rb +1 -1
  51. data/test/aggregate_test.rb +21 -6
  52. data/test/basic_test.rb +3 -3
  53. data/test/duplicate_names_test.rb +2 -2
  54. data/test/enumerable_test.rb +2 -2
  55. data/test/exceptions_test.rb +2 -2
  56. data/test/exclude_threads_test.rb +45 -45
  57. data/test/exec_test.rb +2 -2
  58. data/test/line_number_test.rb +11 -11
  59. data/test/measurement_test.rb +4 -3
  60. data/test/method_elimination_test.rb +74 -0
  61. data/test/module_test.rb +7 -7
  62. data/test/multi_printer_test.rb +81 -0
  63. data/test/no_method_class_test.rb +2 -2
  64. data/test/prime.rb +7 -10
  65. data/test/printers_test.rb +57 -47
  66. data/test/recursive_test.rb +23 -62
  67. data/test/singleton_test.rb +3 -2
  68. data/test/stack_printer_test.rb +74 -0
  69. data/test/stack_test.rb +1 -1
  70. data/test/start_stop_test.rb +2 -2
  71. data/test/test_suite.rb +9 -0
  72. data/test/thread_test.rb +17 -17
  73. data/test/unique_call_path_test.rb +4 -4
  74. metadata +29 -8
@@ -8,7 +8,7 @@ module RubyProf
8
8
  def print(output = STDOUT, options = {})
9
9
  @output = output
10
10
  setup_options(options)
11
-
11
+
12
12
  # add a header - this information is somewhat arbitrary
13
13
  @output << "events: "
14
14
  case RubyProf.measure_mode
@@ -56,7 +56,7 @@ module RubyProf
56
56
  end
57
57
 
58
58
  def print_methods(thread_id, methods)
59
- methods.reverse_each do |method|
59
+ methods.reverse_each do |method|
60
60
  # Print out the file and method name
61
61
  @output << "fl=#{file(method)}\n"
62
62
  @output << "fn=#{method_name(method)}\n"
@@ -0,0 +1,151 @@
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
+ methods.group_by{|m| m.klass_name}.each do |cls, methods|
117
+ # Filter down to just seen methods
118
+ big_methods, small_methods = methods.partition{|m| @seen_methods.include? m}
119
+
120
+ if !big_methods.empty?
121
+ puts "subgraph cluster_#{cls.object_id} {"
122
+ puts "label = \"#{cls}\";"
123
+ puts "fontcolor = #{CLASS_COLOR};"
124
+ puts "fontsize = 16;"
125
+ puts "color = #{CLASS_COLOR};"
126
+ big_methods.each do |m|
127
+ puts "#{m.object_id};"
128
+ end
129
+ puts "}"
130
+ end
131
+ end
132
+ end
133
+
134
+ def print_edges(total_time, method)
135
+ method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
136
+
137
+ target_percentage = (child.target.total_time / total_time) * 100.0
138
+ next if target_percentage < min_percent
139
+
140
+ # Get children method
141
+ puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];"
142
+ end
143
+ end
144
+
145
+ # Silly little helper for printing to the @output
146
+ def puts(str)
147
+ @output.puts(str)
148
+ end
149
+
150
+ end
151
+ end
Binary file
@@ -1,7 +1,7 @@
1
1
  require 'ruby-prof/abstract_printer'
2
2
 
3
3
  module RubyProf
4
- # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
4
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
5
5
  # To use the flat printer:
6
6
  #
7
7
  # result = RubyProf.profile do
@@ -13,28 +13,28 @@ module RubyProf
13
13
  #
14
14
  class FlatPrinter < AbstractPrinter
15
15
  # Print a flat profile report to the provided output.
16
- #
17
- # output - Any IO oject, including STDOUT or a file.
16
+ #
17
+ # output - Any IO oject, including STDOUT or a file.
18
18
  # The default value is STDOUT.
19
- #
20
- # options - Hash of print options. See #setup_options
19
+ #
20
+ # options - Hash of print options. See #setup_options
21
21
  # for more information.
22
22
  #
23
23
  def print(output = STDOUT, options = {})
24
24
  @output = output
25
25
  setup_options(options)
26
26
  print_threads
27
- end
28
-
29
- private
30
-
27
+ end
28
+
29
+ private
30
+
31
31
  def print_threads
32
32
  @result.threads.each do |thread_id, methods|
33
33
  print_methods(thread_id, methods)
34
34
  @output << "\n" * 2
35
35
  end
36
36
  end
37
-
37
+
38
38
  def print_methods(thread_id, methods)
39
39
  # Get total time
40
40
  toplevel = methods.max
@@ -42,27 +42,27 @@ module RubyProf
42
42
  if total_time == 0
43
43
  total_time = 0.01
44
44
  end
45
-
45
+
46
46
  # Now sort methods by largest self time,
47
47
  # not total time like in other printouts
48
48
  methods = methods.sort do |m1, m2|
49
49
  m1.self_time <=> m2.self_time
50
50
  end.reverse
51
-
51
+
52
52
  @output << "Thread ID: %d\n" % thread_id
53
53
  @output << "Total: %0.6f\n" % total_time
54
54
  @output << "\n"
55
55
  @output << " %self total self wait child calls name\n"
56
56
 
57
- sum = 0
57
+ sum = 0
58
58
  methods.each do |method|
59
59
  self_percent = (method.self_time / total_time) * 100
60
60
  next if self_percent < min_percent
61
-
61
+
62
62
  sum += method.self_time
63
63
  #self_time_called = method.called > 0 ? method.self_time/method.called : 0
64
64
  #total_time_called = method.called > 0? method.total_time/method.called : 0
65
-
65
+
66
66
  @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s\n" % [
67
67
  method.self_time / total_time * 100, # %self
68
68
  method.total_time, # total
@@ -75,4 +75,4 @@ module RubyProf
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -1,7 +1,7 @@
1
1
  require 'ruby-prof/abstract_printer'
2
2
  require 'pathname'
3
3
  module RubyProf
4
- # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
4
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
5
5
  # To use the flat printer with line numbers:
6
6
  #
7
7
  # result = RubyProf.profile do
@@ -20,26 +20,26 @@ module RubyProf
20
20
  if total_time == 0
21
21
  total_time = 0.01
22
22
  end
23
-
23
+
24
24
  # Now sort methods by largest self time,
25
25
  # not total time like in other printouts
26
26
  methods = methods.sort do |m1, m2|
27
27
  m1.self_time <=> m2.self_time
28
28
  end.reverse
29
-
29
+
30
30
  @output << "Thread ID: %d\n" % thread_id
31
31
  @output << "Total: %0.6f\n" % total_time
32
32
  @output << "\n"
33
33
  @output << " %self total self wait child calls name\n"
34
- sum = 0
34
+ sum = 0
35
35
  methods.each do |method|
36
36
  self_percent = (method.self_time / total_time) * 100
37
37
  next if self_percent < min_percent
38
-
38
+
39
39
  sum += method.self_time
40
40
  #self_time_called = method.called > 0 ? method.self_time/method.called : 0
41
41
  #total_time_called = method.called > 0? method.total_time/method.called : 0
42
-
42
+
43
43
  @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s " % [
44
44
  method.self_time / total_time * 100, # %self
45
45
  method.total_time, # total
@@ -47,26 +47,26 @@ module RubyProf
47
47
  method.wait_time, # wait
48
48
  method.children_time, # children
49
49
  method.called, # calls
50
- method_name(method), # name
50
+ method_name(method), # name
51
51
  ]
52
52
  if method.source_file != 'ruby_runtime'
53
53
  @output << " %s:%s" % [File.expand_path(method.source_file), method.line]
54
54
  end
55
55
  @output << "\n\tcalled from: "
56
-
56
+
57
57
  # make sure they're unique
58
- method.call_infos.map{|ci|
58
+ method.call_infos.map{|ci|
59
59
  if ci.parent && ci.parent.target.source_file != 'ruby_runtime'
60
60
  [method_name(ci.parent.target), File.expand_path(ci.parent.target.source_file), ci.parent.target.line]
61
61
  else
62
62
  nil
63
- end
63
+ end
64
64
  }.compact.uniq.each{|args|
65
- @output << " %s (%s:%s) " % args
65
+ @output << " %s (%s:%s) " % args
66
66
  }
67
- @output << "\n\n"
67
+ @output << "\n\n"
68
68
  end
69
69
  end
70
70
  end
71
- end
71
+ end
72
72
 
@@ -2,8 +2,8 @@ require 'ruby-prof/abstract_printer'
2
2
  require 'erb'
3
3
 
4
4
  module RubyProf
5
- # Generates graph[link:files/examples/graph_html.html] profile reports as html.
6
- # To use the grap html printer:
5
+ # Generates graph[link:files/examples/graph_html.html] profile reports as html.
6
+ # To use the graph html printer:
7
7
  #
8
8
  # result = RubyProf.profile do
9
9
  # [code to profile]
@@ -14,20 +14,20 @@ module RubyProf
14
14
  #
15
15
  # The constructor takes two arguments. The first is
16
16
  # a RubyProf::Result object generated from a profiling
17
- # run. The second is the minimum %total (the methods
17
+ # run. The second is the minimum %total (the methods
18
18
  # total time divided by the overall total time) that
19
- # a method must take for it to be printed out in
19
+ # a method must take for it to be printed out in
20
20
  # the report. Use this parameter to eliminate methods
21
21
  # that are not important to the overall profiling results.
22
-
22
+
23
23
  class GraphHtmlPrinter < AbstractPrinter
24
24
  include ERB::Util
25
-
25
+
26
26
  PERCENTAGE_WIDTH = 8
27
27
  TIME_WIDTH = 10
28
28
  CALL_WIDTH = 20
29
-
30
- # Create a GraphPrinter. Result is a RubyProf::Result
29
+
30
+ # Create a GraphPrinter. Result is a RubyProf::Result
31
31
  # object generated from a profiling run.
32
32
  def initialize(result)
33
33
  super(result)
@@ -36,17 +36,17 @@ module RubyProf
36
36
  end
37
37
 
38
38
  # Print a graph html report to the provided output.
39
- #
40
- # output - Any IO oject, including STDOUT or a file.
39
+ #
40
+ # output - Any IO oject, including STDOUT or a file.
41
41
  # The default value is STDOUT.
42
- #
43
- # options - Hash of print options. See #setup_options
42
+ #
43
+ # options - Hash of print options. See #setup_options
44
44
  # for more information.
45
45
  #
46
46
  def print(output = STDOUT, options = {})
47
47
  @output = output
48
48
  setup_options(options)
49
-
49
+
50
50
  filename = options[:filename]
51
51
  template = filename ? File.read(filename).untaint : (options[:template] || self.template)
52
52
  _erbout = @output
@@ -55,38 +55,46 @@ module RubyProf
55
55
  @output << erb.result(binding)
56
56
  end
57
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
+
58
66
  # These methods should be private but then ERB doesn't
59
- # work. Turn off RDOC though
67
+ # work. Turn off RDOC though
60
68
  #--
61
69
  def calculate_thread_times
62
70
  # Cache thread times since this is an expensive
63
- # operation with the required sorting
71
+ # operation with the required sorting
72
+ @overall_threads_time = 0.0
73
+ @thread_times = Hash.new
64
74
  @result.threads.each do |thread_id, methods|
65
- top = methods.max
66
-
67
- thread_time = 0.01
68
- thread_time = top.total_time if top.total_time > 0
69
-
70
- @thread_times[thread_id] = thread_time
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
71
79
  end
72
80
  end
73
-
81
+
74
82
  def thread_time(thread_id)
75
83
  @thread_times[thread_id]
76
84
  end
77
-
85
+
78
86
  def total_percent(thread_id, method)
79
87
  overall_time = self.thread_time(thread_id)
80
88
  (method.total_time/overall_time) * 100
81
89
  end
82
-
90
+
83
91
  def self_percent(method)
84
92
  overall_time = self.thread_time(method.thread_id)
85
93
  (method.self_time/overall_time) * 100
86
94
  end
87
95
 
88
96
  # Creates a link to a method. Note that we do not create
89
- # links to methods which are under the min_perecent
97
+ # links to methods which are under the min_perecent
90
98
  # specified by the user, since they will not be
91
99
  # printed out.
92
100
  def create_link(thread_id, method)
@@ -95,14 +103,27 @@ module RubyProf
95
103
  h method.full_name
96
104
  else
97
105
  href = '#' + method_href(thread_id, method)
98
- "<a href=\"#{href}\">#{h method.full_name}</a>"
106
+ "<a href=\"#{href}\">#{h method.full_name}</a>"
99
107
  end
100
108
  end
101
-
109
+
102
110
  def method_href(thread_id, method)
103
111
  h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s)
104
112
  end
105
-
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
+
106
127
  def template
107
128
  '
108
129
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
@@ -149,7 +170,7 @@ module RubyProf
149
170
  td {
150
171
  border-left: 1px solid #CCC;
151
172
  text-align: center;
152
- }
173
+ }
153
174
 
154
175
  .method_name {
155
176
  text-align: left;
@@ -164,7 +185,7 @@ module RubyProf
164
185
  <th>Thread ID</th>
165
186
  <th>Total Time</th>
166
187
  </tr>
167
- <% for thread_id, methods in @result.threads %>
188
+ <% for thread_id in @result.threads.keys.sort %>
168
189
  <tr>
169
190
  <td><a href="#<%= thread_id %>"><%= thread_id %></a></td>
170
191
  <td><%= thread_time(thread_id) %></td>
@@ -173,7 +194,8 @@ module RubyProf
173
194
  </table>
174
195
 
175
196
  <!-- Methods Tables -->
176
- <% for thread_id, methods in @result.threads
197
+ <% for thread_id in @result.threads.keys.sort
198
+ methods = @result.threads[thread_id]
177
199
  total_time = thread_time(thread_id) %>
178
200
  <h2><a name="<%= thread_id %>">Thread <%= thread_id %></a></h2>
179
201
 
@@ -196,7 +218,7 @@ module RubyProf
196
218
  next if total_percentage < min_percent
197
219
  next if min_time && method.total_time < min_time
198
220
  self_percentage = (method.self_time/total_time) * 100 %>
199
-
221
+
200
222
  <!-- Parents -->
201
223
  <% for caller in method.aggregate_parents.sort_by(&:total_time)
202
224
  next unless caller.parent
@@ -211,7 +233,7 @@ module RubyProf
211
233
  <% called = "#{caller.called}/#{method.called}" %>
212
234
  <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
213
235
  <td class="method_name"><%= create_link(thread_id, caller.parent.target) %></td>
214
- <td><a href="file://<%=h srcfile=File.expand_path(caller.parent.target.source_file) %>#line=<%= linenum=caller.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= caller.line %></a></td>
236
+ <td><%= file_link(caller.parent.target.source_file, caller.line) %></td>
215
237
  </tr>
216
238
  <% end %>
217
239
 
@@ -224,7 +246,7 @@ module RubyProf
224
246
  <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
225
247
  <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
226
248
  <td class="method_name"><a name="<%= method_href(thread_id, method) %>"><%= h method.full_name %></a></td>
227
- <td><a href="file://<%=h srcfile=File.expand_path(method.source_file) %>#line=<%= linenum=method.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= method.line %></a></td>
249
+ <td><%= file_link(method.source_file, method.line) %></td>
228
250
  </tr>
229
251
 
230
252
  <!-- Children -->
@@ -240,7 +262,7 @@ module RubyProf
240
262
  <% called = "#{callee.called}/#{callee.target.called}" %>
241
263
  <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
242
264
  <td class="method_name"><%= create_link(thread_id, callee.target) %></td>
243
- <td><a href="file://<%=h srcfile=File.expand_path(method.source_file) %>#line=<%= linenum=callee.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= callee.line %></a></td>
265
+ <td><%= file_link(method.source_file, callee.line) %></td>
244
266
  </tr>
245
267
  <% end %>
246
268
  <!-- Create divider row -->
@@ -252,5 +274,5 @@ module RubyProf
252
274
  </html>'
253
275
  end
254
276
  end
255
- end
277
+ end
256
278