ruby-prof-danielhoey 0.8.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 (72) hide show
  1. data/CHANGES +221 -0
  2. data/LICENSE +23 -0
  3. data/README +432 -0
  4. data/Rakefile +158 -0
  5. data/bin/ruby-prof +224 -0
  6. data/examples/flat.txt +55 -0
  7. data/examples/graph.html +823 -0
  8. data/examples/graph.txt +170 -0
  9. data/ext/ruby_prof/call_tree.c +392 -0
  10. data/ext/ruby_prof/call_tree.h +32 -0
  11. data/ext/ruby_prof/extconf.rb +40 -0
  12. data/ext/ruby_prof/list.c +66 -0
  13. data/ext/ruby_prof/list.h +10 -0
  14. data/ext/ruby_prof/measure_allocations.h +58 -0
  15. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  16. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  17. data/ext/ruby_prof/measure_gc_time.h +57 -0
  18. data/ext/ruby_prof/measure_memory.h +101 -0
  19. data/ext/ruby_prof/measure_process_time.h +52 -0
  20. data/ext/ruby_prof/measure_wall_time.h +53 -0
  21. data/ext/ruby_prof/measurement.h +13 -0
  22. data/ext/ruby_prof/mingw/Rakefile +23 -0
  23. data/ext/ruby_prof/mingw/build.rake +38 -0
  24. data/ext/ruby_prof/ruby_prof.c +1943 -0
  25. data/ext/ruby_prof/ruby_prof.h +183 -0
  26. data/ext/ruby_prof/version.h +4 -0
  27. data/lib/ruby-prof.rb +59 -0
  28. data/lib/ruby-prof/abstract_printer.rb +41 -0
  29. data/lib/ruby-prof/aggregate_call_info.rb +62 -0
  30. data/lib/ruby-prof/call_info.rb +47 -0
  31. data/lib/ruby-prof/call_tree/abstract_printer.rb +24 -0
  32. data/lib/ruby-prof/call_tree/html_printer.rb +89 -0
  33. data/lib/ruby-prof/call_tree/html_printer_output.html.erb +99 -0
  34. data/lib/ruby-prof/call_tree/text_printer.rb +28 -0
  35. data/lib/ruby-prof/call_tree_printer.rb +84 -0
  36. data/lib/ruby-prof/flat_printer.rb +78 -0
  37. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  38. data/lib/ruby-prof/graph_html_printer.rb +256 -0
  39. data/lib/ruby-prof/graph_printer.rb +157 -0
  40. data/lib/ruby-prof/method_info.rb +111 -0
  41. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  42. data/lib/ruby-prof/task.rb +146 -0
  43. data/lib/ruby-prof/test.rb +148 -0
  44. data/lib/unprof.rb +8 -0
  45. data/rails/environment/profile.rb +24 -0
  46. data/rails/example/example_test.rb +9 -0
  47. data/rails/profile_test_helper.rb +21 -0
  48. data/test/aggregate_test.rb +121 -0
  49. data/test/basic_test.rb +290 -0
  50. data/test/current_failures_windows +8 -0
  51. data/test/do_nothing.rb +0 -0
  52. data/test/duplicate_names_test.rb +32 -0
  53. data/test/enumerable_test.rb +16 -0
  54. data/test/exceptions_test.rb +15 -0
  55. data/test/exclude_threads_test.rb +54 -0
  56. data/test/exec_test.rb +14 -0
  57. data/test/line_number_test.rb +73 -0
  58. data/test/measurement_test.rb +121 -0
  59. data/test/module_test.rb +54 -0
  60. data/test/no_method_class_test.rb +14 -0
  61. data/test/prime.rb +58 -0
  62. data/test/prime_test.rb +13 -0
  63. data/test/printers_test.rb +130 -0
  64. data/test/recursive_test.rb +275 -0
  65. data/test/ruby-prof-bin +20 -0
  66. data/test/singleton_test.rb +37 -0
  67. data/test/stack_test.rb +138 -0
  68. data/test/start_stop_test.rb +95 -0
  69. data/test/test_suite.rb +23 -0
  70. data/test/thread_test.rb +173 -0
  71. data/test/unique_call_path_test.rb +225 -0
  72. metadata +163 -0
@@ -0,0 +1,183 @@
1
+ /*
2
+ * Copyright (C) 2008 Shugo Maeda <shugo@ruby-lang.org>
3
+ * Charlie Savage <cfis@savagexi.com>
4
+ * All rights reserved.
5
+ *
6
+ * Redistribution and use in source and binary forms, with or without
7
+ * modification, are permitted provided that the following conditions
8
+ * are met:
9
+ * 1. Redistributions of source code must retain the above copyright
10
+ * notice, this list of conditions and the following disclaimer.
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in the
13
+ * documentation and/or other materials provided with the distribution.
14
+ *
15
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
16
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
19
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
+ * SUCH DAMAGE.
26
+ */
27
+
28
+ /* ruby-prof tracks the time spent executing every method in ruby programming.
29
+ The main players are:
30
+
31
+ prof_result_t - Its one field, values, contains the overall results
32
+ thread_data_t - Stores data about a single thread.
33
+ prof_stack_t - The method call stack in a particular thread
34
+ prof_method_t - Profiling information for each method
35
+ prof_call_info_t - Keeps track a method's callers and callees.
36
+
37
+ The final resulut is a hash table of thread_data_t, keyed on the thread
38
+ id. Each thread has an hash a table of prof_method_t, keyed on the
39
+ method id. A hash table is used for quick look up when doing a profile.
40
+ However, it is exposed to Ruby as an array.
41
+
42
+ Each prof_method_t has two hash tables, parent and children, of prof_call_info_t.
43
+ These objects keep track of a method's callers (who called the method) and its
44
+ callees (who the method called). These are keyed the method id, but once again,
45
+ are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
46
+ caller or callee method, thereby making it easy to navigate through the call
47
+ hierarchy in ruby - which is very helpful for creating call graphs.
48
+ */
49
+
50
+ /* #define DEBUG */
51
+
52
+ #ifndef RUBY_PROF_H
53
+ #define RUBY_PROF_H
54
+
55
+ #include <stdio.h>
56
+
57
+ #include <ruby.h>
58
+
59
+ #ifndef RUBY_VM
60
+ #include <node.h>
61
+ #include <st.h>
62
+ typedef rb_event_t rb_event_flag_t;
63
+ #define rb_sourcefile() (node ? node->nd_file : 0)
64
+ #define rb_sourceline() (node ? nd_line(node) : 0)
65
+ #endif
66
+
67
+ #include "version.h"
68
+
69
+ /* ================ Constants =================*/
70
+ #define INITIAL_STACK_SIZE 8
71
+ #define INITIAL_CALL_INFOS_SIZE 2
72
+
73
+
74
+ /* ================ Measurement =================*/
75
+ #include "measurement.h"
76
+ #include "measure_process_time.h"
77
+ #include "measure_wall_time.h"
78
+ #include "measure_cpu_time.h"
79
+ #include "measure_allocations.h"
80
+ #include "measure_memory.h"
81
+ #include "measure_gc_runs.h"
82
+ #include "measure_gc_time.h"
83
+
84
+ prof_measure_t (*get_measurement)() = measure_process_time;
85
+ double (*convert_measurement)(prof_measure_t) = convert_process_time;
86
+
87
+ /* ================ DataTypes =================*/
88
+ static VALUE mProf;
89
+ static VALUE cResult;
90
+ static VALUE cMethodInfo;
91
+ static VALUE cCallInfo;
92
+
93
+ /* Profiling information for each method. */
94
+ typedef struct {
95
+ VALUE klass; /* The method's class. */
96
+ ID mid; /* The method id. */
97
+ int depth; /* The recursion depth. */
98
+ int key; /* Cache calculated key */
99
+ } prof_method_key_t;
100
+
101
+ struct prof_call_infos_t;
102
+
103
+ /* Profiling information for each method. */
104
+ typedef struct {
105
+ prof_method_key_t *key; /* Method key */
106
+ const char *source_file; /* The method's source file */
107
+ int line; /* The method's line number. */
108
+ int active; /* Is this recursion depth. */
109
+ struct prof_call_infos_t *call_infos; /* Call info objects for this method */
110
+ VALUE object; /* Cahced ruby object */
111
+ } prof_method_t;
112
+
113
+ /* Callers and callee information for a method. */
114
+ typedef struct prof_call_info_t {
115
+ prof_method_t *target; /* Use target instead of method to avoid conflict with Ruby method */
116
+ struct prof_call_info_t *parent;
117
+ st_table *call_infos;
118
+ int called;
119
+ prof_measure_t total_time;
120
+ prof_measure_t self_time;
121
+ prof_measure_t wait_time;
122
+ int line;
123
+ VALUE object;
124
+ VALUE children;
125
+ } prof_call_info_t;
126
+
127
+ /* Array of call_info objects */
128
+ typedef struct prof_call_infos_t {
129
+ prof_call_info_t **start;
130
+ prof_call_info_t **end;
131
+ prof_call_info_t **ptr;
132
+ VALUE object;
133
+ } prof_call_infos_t;
134
+
135
+
136
+ /* Temporary object that maintains profiling information
137
+ for active methods - there is one per method.*/
138
+ typedef struct {
139
+ /* Caching prof_method_t values significantly
140
+ increases performance. */
141
+ prof_call_info_t *call_info;
142
+ prof_measure_t start_time;
143
+ prof_measure_t wait_time;
144
+ prof_measure_t child_time;
145
+ unsigned int line;
146
+ } prof_frame_t;
147
+
148
+ /* Current stack of active methods.*/
149
+ typedef struct {
150
+ prof_frame_t *start;
151
+ prof_frame_t *end;
152
+ prof_frame_t *ptr;
153
+ } prof_stack_t;
154
+
155
+ /* Profiling information for a thread. */
156
+ typedef struct {
157
+ VALUE thread_id; /* Thread id */
158
+ st_table* method_table; /* Methods called in the thread */
159
+ prof_stack_t* stack; /* Active methods */
160
+ prof_measure_t last_switch; /* Point of last context switch */
161
+ } thread_data_t;
162
+
163
+ typedef struct {
164
+ VALUE threads;
165
+ } prof_result_t;
166
+
167
+
168
+ /* ================ Variables =================*/
169
+ static int measure_mode;
170
+ static st_table *threads_tbl = NULL;
171
+ static st_table *exclude_threads_tbl = NULL;
172
+
173
+ /* TODO - If Ruby become multi-threaded this has to turn into
174
+ a separate stack since this isn't thread safe! */
175
+ static thread_data_t* last_thread_data = NULL;
176
+
177
+
178
+ /* Forward declarations */
179
+ static VALUE prof_call_infos_wrap(prof_call_infos_t *call_infos);
180
+ static VALUE prof_call_info_wrap(prof_call_info_t *call_info);
181
+ static VALUE prof_method_wrap(prof_method_t *result);
182
+
183
+ #endif
@@ -0,0 +1,4 @@
1
+ #define RUBY_PROF_VERSION "0.8.1"
2
+ #define RUBY_PROF_VERSION_MAJ 0
3
+ #define RUBY_PROF_VERSION_MIN 8
4
+ #define RUBY_PROF_VERSION_MIC 1
@@ -0,0 +1,59 @@
1
+ # require the .so file
2
+ me = File.dirname(__FILE__) + '/'
3
+ begin
4
+ # fat binaries
5
+ require "#{me}/#{RUBY_VERSION[0..2]}/ruby_prof"
6
+ rescue Exception
7
+ require "#{me}/../ext/ruby_prof/ruby_prof"
8
+ end
9
+
10
+ require "ruby-prof/method_info"
11
+ require "ruby-prof/call_info"
12
+ require "ruby-prof/aggregate_call_info"
13
+ require "ruby-prof/flat_printer"
14
+ require "ruby-prof/flat_printer_with_line_numbers"
15
+ require "ruby-prof/graph_printer"
16
+ require "ruby-prof/graph_html_printer"
17
+ require "ruby-prof/call_tree_printer"
18
+ require "ruby-prof/symbol_to_proc" # for 1.8's benefit
19
+ #require "ruby-prof/result"
20
+ require "ruby-prof/call_tree/abstract_printer"
21
+ require "ruby-prof/call_tree/text_printer"
22
+ require "ruby-prof/call_tree/html_printer"
23
+
24
+ module RubyProf
25
+ # See if the user specified the clock mode via
26
+ # the RUBY_PROF_MEASURE_MODE environment variable
27
+ def self.figure_measure_mode
28
+ case ENV["RUBY_PROF_MEASURE_MODE"]
29
+ when "wall" || "wall_time"
30
+ RubyProf.measure_mode = RubyProf::WALL_TIME
31
+ when "cpu" || "cpu_time"
32
+ if ENV.key?("RUBY_PROF_CPU_FREQUENCY")
33
+ RubyProf.cpu_frequency = ENV["RUBY_PROF_CPU_FREQUENCY"].to_f
34
+ else
35
+ begin
36
+ open("/proc/cpuinfo") do |f|
37
+ f.each_line do |line|
38
+ s = line.slice(/cpu MHz\s*:\s*(.*)/, 1)
39
+ if s
40
+ RubyProf.cpu_frequency = s.to_f * 1000000
41
+ break
42
+ end
43
+ end
44
+ end
45
+ rescue Errno::ENOENT
46
+ end
47
+ end
48
+ RubyProf.measure_mode = RubyProf::CPU_TIME
49
+ when "allocations"
50
+ RubyProf.measure_mode = RubyProf::ALLOCATIONS
51
+ when "memory"
52
+ RubyProf.measure_mode = RubyProf::MEMORY
53
+ else
54
+ RubyProf.measure_mode = RubyProf::PROCESS_TIME
55
+ end
56
+ end
57
+ end
58
+
59
+ RubyProf::figure_measure_mode
@@ -0,0 +1,41 @@
1
+ module RubyProf
2
+ class AbstractPrinter
3
+ def initialize(result)
4
+ @result = result
5
+ @output = nil
6
+ @options = {}
7
+ end
8
+
9
+ # Specify print options.
10
+ #
11
+ # options - Hash table
12
+ # :min_percent - Number 0 to 100 that specifes the minimum
13
+ # %self (the methods self time divided by the
14
+ # overall total time) that a method must take
15
+ # for it to be printed out in the report.
16
+ # Default value is 0.
17
+ #
18
+ # :print_file - True or false. Specifies if a method's source
19
+ # file should be printed. Default value if false.
20
+ #
21
+ def setup_options(options = {})
22
+ @options = options
23
+ end
24
+
25
+ def min_percent
26
+ @options[:min_percent] || 0
27
+ end
28
+
29
+ def print_file
30
+ @options[:print_file] || false
31
+ end
32
+
33
+ def method_name(method)
34
+ name = method.full_name
35
+ if print_file
36
+ name += " (#{method.source_file}:#{method.line}}"
37
+ end
38
+ name
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ module RubyProf
2
+ class AggregateCallInfo
3
+ attr_reader :call_infos
4
+ def initialize(call_infos)
5
+ if call_infos.length == 0
6
+ raise(ArgumentError, "Must specify at least one call info.")
7
+ end
8
+ @call_infos = call_infos
9
+ end
10
+
11
+ def target
12
+ call_infos.first.target
13
+ end
14
+
15
+ def parent
16
+ call_infos.first.parent
17
+ end
18
+
19
+ def line
20
+ call_infos.first.line
21
+ end
22
+
23
+ def children
24
+ call_infos.inject(Array.new) do |result, call_info|
25
+ result.concat(call_info.children)
26
+ end
27
+ end
28
+
29
+ def total_time
30
+ aggregate(:total_time)
31
+ end
32
+
33
+ def self_time
34
+ aggregate(:self_time)
35
+ end
36
+
37
+ def wait_time
38
+ aggregate(:wait_time)
39
+ end
40
+
41
+ def children_time
42
+ aggregate(:children_time)
43
+ end
44
+
45
+ def called
46
+ aggregate(:called)
47
+ end
48
+
49
+ def to_s
50
+ "#{call_infos.first.full_name}"
51
+ end
52
+
53
+ private
54
+
55
+ def aggregate(method_name)
56
+ self.call_infos.inject(0) do |sum, call_info|
57
+ sum += call_info.send(method_name)
58
+ sum
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ module RubyProf
2
+ class CallInfo
3
+ def depth
4
+ result = 0
5
+ call_info = self.parent
6
+
7
+ while call_info
8
+ result += 1
9
+ call_info = call_info.parent
10
+ end
11
+ result
12
+ end
13
+
14
+ def children_time
15
+ children.inject(0) do |sum, call_info|
16
+ sum += call_info.total_time
17
+ end
18
+ end
19
+
20
+ def stack
21
+ @stack ||= begin
22
+ methods = Array.new
23
+ call_info = self
24
+
25
+ while call_info
26
+ methods << call_info.target
27
+ call_info = call_info.parent
28
+ end
29
+ methods.reverse
30
+ end
31
+ end
32
+
33
+ def call_sequence
34
+ @call_sequence ||= begin
35
+ stack.map {|method| method.full_name}.join('->')
36
+ end
37
+ end
38
+
39
+ def root?
40
+ self.parent.nil?
41
+ end
42
+
43
+ def to_s
44
+ "#{call_sequence}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module RubyProf
2
+ class CallTreeAbstractPrinter
3
+ def initialize(call_tree, min_percentage=0)
4
+ @call_tree = call_tree
5
+ @min_percentage = min_percentage.to_f
6
+ end
7
+
8
+ def print(io)
9
+ print_methods(io, @call_tree.children)
10
+ end
11
+
12
+ def print_methods(io, methods, parent_time=nil)
13
+ methods.sort_by{|m| m.time}.reverse.each do |method|
14
+ io << format_method(method)
15
+ next if parent_time and method.time < parent_time * @min_percentage / 100
16
+ print_methods(io, method.children, method.time)
17
+ end
18
+ end
19
+
20
+ def format_method(method)
21
+ raise "abstract"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,89 @@
1
+ require 'erb'
2
+ require 'rexml/document'
3
+ require 'cgi'
4
+
5
+ module RubyProf
6
+ class CallTreeHtmlPrinter < CallTreeAbstractPrinter
7
+ def initialize(call_tree, min_percentage=2)
8
+ super(call_tree, min_percentage)
9
+ @total_time = call_tree.children.inject(0){|s, c| s+=c.time}
10
+ end
11
+
12
+ def print(io)
13
+ @result = print_methods(@call_tree.children, @call_tree.time)
14
+ @result = "<div id='main'>main (#{@total_time}s)\n #{@result}\n</div>"
15
+ formatted_result = ''
16
+ REXML::Document.new(@result).write(formatted_result, 2)
17
+ @result = formatted_result
18
+ erb = ERB.new(page_template, nil, nil)
19
+ io << erb.result(binding)
20
+ end
21
+
22
+ def print_methods(method_calls, parent_time)
23
+ result = ''
24
+
25
+ significant_method_calls = method_calls.find_all{|call| call.time >= (parent_time * @min_percentage.to_f / 100) and percentage(call.time) >= @min_percentage/2}
26
+ significant_method_calls.sort_by{|m| m.time}.reverse.each do |method|
27
+ @method = method
28
+ if method.children.empty?
29
+ erb = ERB.new(leaf_template, nil, nil)
30
+ else
31
+ erb = ERB.new(node_template, nil, nil)
32
+ end
33
+ result << erb.result(binding)
34
+ end
35
+
36
+ return result
37
+
38
+ @insignificant_method_calls = method_calls - significant_method_calls
39
+ unless @insignificant_method_calls.empty?
40
+ erb = ERB.new(insignificant_calls_template, nil, nil)
41
+ result << erb.result(binding)
42
+ end
43
+
44
+ result
45
+ end
46
+
47
+ def print_leaf(method_call)
48
+ @method = method_call
49
+ ERB.new(leaf_template, nil, nil).result(binding)
50
+ end
51
+
52
+ def percentage(time)
53
+ ((time * 100) / @total_time).to_i
54
+ end
55
+
56
+ def page_template
57
+ @page_template ||= File.read("#{File.dirname(__FILE__)}/html_printer_output.html.erb")
58
+ end
59
+
60
+ def node_template
61
+ %Q{<div class="call_tree_node" time="#{percentage(@method.time)}" onclick="CallTree.click(this, event)">#{call_summary(@method)}
62
+ <span class="nodes_not_shown">...</span>
63
+ <%= print_methods(@method.children, method.time) %>
64
+ </div>}.strip
65
+ end
66
+
67
+ def leaf_template
68
+ %Q{<div class="call_tree_node leaf" time="#{percentage(@method.time)}">#{call_summary(@method)}</div>}
69
+ end
70
+
71
+ def insignificant_calls_template
72
+ %Q{<div class="hide_child_nodes" onclick="CallTree.click(this, event)">
73
+ <div class="nodes_not_shown">...</div>
74
+ <% @insignificant_method_calls.each do |call| %>
75
+ <%= print_leaf(call) %>
76
+ <% end %>
77
+ </div>}.strip
78
+ end
79
+
80
+ def call_summary(call)
81
+ klass, method = %w(klass method).collect{|m| CGI.escapeHTML(call.send(m).to_s)}
82
+ if percentage(call.time) < 1
83
+ "#{klass}::#{method}"
84
+ else
85
+ "<span class='klass_and_method'>#{klass}::#{method}</span> - <span class='percentage'>#{percentage(call.time)}%</span> <span class='extra_info'>(<span class='file'>#{call.file},</span> <span class='time'>#{(call.time*1000).to_i}ms,</span> <span class='call_count'>#{call.call_count} calls</span>)</span>"
86
+ end
87
+ end
88
+ end
89
+ end