rdp-jruby-prof 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,53 @@
1
+
2
+ jruby-prof
3
+ ==========
4
+
5
+ Daniel Lucraft
6
+ http://danlucraft.com/blog/2010/03/jruby-prof/
7
+
8
+ This is a clone of ruby-prof for JRuby. It supports all the usual output
9
+ modes, plus a call tree output mode.
10
+
11
+ The only measure mode it currently supports is wall time.
12
+
13
+ *** NB. This is really new code. It probably has lots of bugs and things. - March 2010 ***
14
+
15
+ INSTALL
16
+ -------
17
+
18
+ $ jruby -S gem install jruby-prof
19
+
20
+ USAGE
21
+ -----
22
+
23
+ In your program:
24
+
25
+ require 'rubygems'
26
+ require 'jruby-prof'
27
+
28
+ result = JRubyProf.profile do
29
+ # my stuff
30
+ end
31
+
32
+ JRubyProf.print_flat_text(result, "flat.txt")
33
+ JRubyProf.print_graph_text(result, "graph.txt")
34
+ JRubyProf.print_graph_html(result, "graph.html")
35
+ JRubyProf.print_call_tree(result, "call_tree.txt")
36
+ JRubyProf.print_tree_html(result, "call_tree.html")
37
+
38
+ In order to tell JRuby to send events to JRubyProf for processing, you must
39
+ run your script with the debug flag on:
40
+
41
+ $ jruby --debug my_script.rb
42
+
43
+
44
+ Or you can use its gem script:
45
+
46
+ $ jruby --debug -S jruby-prof scriptname.rb
47
+
48
+ Which will profile the entire script, create a local directory named "profile" and output one copy of each graph type there.
49
+
50
+ LICENSE
51
+ -------
52
+
53
+ MIT
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'jruby-prof'
3
+ original_dir = Dir.pwd
4
+
5
+ if ARGV.detect{|arg| arg == '-h' || arg == '--help'}
6
+ puts 'syntax: jruby --debug -S jruby-prof script_name arg1 arg2 arg3 (writes the several outputs available to profile/*)'
7
+ exit 1
8
+ end
9
+
10
+ ruby_script = ARGV.shift
11
+ result = JRubyProf.profile {
12
+ require ruby_script
13
+ }
14
+
15
+ Dir.chdir(original_dir) do
16
+ require 'fileutils'
17
+ FileUtils.mkdir_p "profile"
18
+ JRubyProf.print_flat_text(result, "profile/flat.txt")
19
+ JRubyProf.print_graph_text(result, "profile/graph.txt")
20
+ JRubyProf.print_graph_html(result, "profile/graph.html")
21
+ JRubyProf.print_call_tree(result, "profile/call_tree.txt")
22
+ JRubyProf.print_tree_html(result, "profile/call_tree.html")
23
+ end
Binary file
@@ -0,0 +1,66 @@
1
+
2
+ require 'java'
3
+ require 'erb'
4
+
5
+ require File.dirname(__FILE__) + "/jruby-prof.jar"
6
+
7
+ import org.jruby.prof.JRubyProf
8
+
9
+ require 'jruby-prof/abstract_printer'
10
+ require 'jruby-prof/simple_tree_printer'
11
+ require 'jruby-prof/flat_text_printer'
12
+ require 'jruby-prof/graph_text_printer'
13
+ require 'jruby-prof/graph_html_printer'
14
+ require 'jruby-prof/invocation_set'
15
+ require 'jruby-prof/method'
16
+ require 'jruby-prof/tree_html_printer'
17
+
18
+ require 'jruby-prof/profile_invocation'
19
+
20
+ class JRubyProf
21
+
22
+ def self.start
23
+ raise RuntimeError, "JRubyProf already running" if running?
24
+ start_tracing
25
+ end
26
+
27
+ def self.stop
28
+ stop_tracing
29
+ end
30
+
31
+ def self.running?
32
+ self.is_running
33
+ end
34
+
35
+ def self.profile
36
+ raise ArgumentError, "profile requires a block" unless block_given?
37
+ start
38
+ yield
39
+ stop
40
+ end
41
+
42
+ def self.print_call_tree(result, filename)
43
+ printer = SimpleTreePrinter.new(ThreadSet.new(result.values.to_a, JRubyProf.lastTracingDuration))
44
+ printer.print_to_file(filename)
45
+ end
46
+
47
+ def self.print_tree_html(result, filename)
48
+ printer = TreeHtmlPrinter.new(ThreadSet.new(result.values.to_a, JRubyProf.lastTracingDuration))
49
+ printer.print_to_file(filename)
50
+ end
51
+
52
+ def self.print_flat_text(result, filename)
53
+ printer = FlatTextPrinter.new(ThreadSet.new(result.values.to_a, JRubyProf.lastTracingDuration))
54
+ printer.print_to_file(filename)
55
+ end
56
+
57
+ def self.print_graph_text(result, filename)
58
+ printer = GraphTextPrinter.new(ThreadSet.new(result.values.to_a, JRubyProf.lastTracingDuration))
59
+ printer.print_to_file(filename)
60
+ end
61
+
62
+ def self.print_graph_html(result, filename)
63
+ printer = GraphHtmlPrinter.new(ThreadSet.new(result.values.to_a, JRubyProf.lastTracingDuration))
64
+ printer.print_to_file(filename)
65
+ end
66
+ end
@@ -0,0 +1,21 @@
1
+
2
+ class JRubyProf
3
+ class AbstractPrinter
4
+ attr_reader :thread_set
5
+
6
+ def initialize(thread_set, options={})
7
+ @thread_set = thread_set
8
+ @options = options
9
+ end
10
+
11
+ def print_to_file(filename)
12
+ puts "Dumping trace to #{File.expand_path(filename)}"
13
+ File.open(filename, "w") do |f|
14
+ print_on(f)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+
@@ -0,0 +1,35 @@
1
+
2
+ class JRubyProf
3
+ class FlatTextPrinter < AbstractPrinter
4
+ TABLE_HEADER = " %self cumulative total self children calls self/call total/call name"
5
+
6
+ def print_on(output)
7
+ thread_set.invocations.each_with_index do |invocation, i|
8
+ output.puts
9
+ output.puts "Thread #{i + 1} / #{thread_set.length}"
10
+ output.puts
11
+ methods = invocation.get_methods.values.sort_by {|m| m.duration }.reverse
12
+ output.puts TABLE_HEADER
13
+ total_duration = 0
14
+ rows = methods.map do |method|
15
+ total = method.duration
16
+ children = method.childrens_duration
17
+ self_ = total - children
18
+ total_duration += self_ unless self_ < 0
19
+ next if method.name == "#"
20
+ [total, children, self_, method.count, method.name]
21
+ end
22
+ cumulative = 0
23
+ rows = rows.compact.sort_by {|r| r[2]}.reverse
24
+ rows.each do |row|
25
+ total, children, self_, count, name = *row
26
+ cumulative += self_
27
+ self_pc = (self_.to_f/total_duration)*100
28
+ self_per_call = self_.to_f/count
29
+ total_per_call = total.to_f/count
30
+ output.puts "#{("%2.2f" % self_pc).rjust(6)} #{cumulative.to_s.rjust(11)} #{total.to_s.rjust(8)} #{self_.to_s.rjust(7)} #{children.to_s.rjust(8)} #{count.to_s.rjust(7)} #{("%2.2f" % self_per_call).to_s.rjust(9)} #{("%2.2f" % total_per_call).to_s.rjust(10)} #{name}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,123 @@
1
+
2
+ class JRubyProf
3
+ class GraphHtmlPrinter < GraphTextPrinter
4
+ def print_on(output)
5
+ output.puts HEADER
6
+ total_duration = thread_set.duration
7
+ thread_set.invocations.each_with_index do |thread, i|
8
+ methods = thread.get_methods.values.sort_by {|m| m.duration }.reverse
9
+ output.puts "<h3>Thread #{i + 1}/#{thread_set.length}</h3>"
10
+ output.puts TABLE_HEADER
11
+ rows = methods.map do |method|
12
+ method.parent_contexts.each do |context|
13
+ print_method(output, context, total_duration, false)
14
+ end
15
+ print_method(output, method, total_duration, true)
16
+ method.child_contexts.each do |context|
17
+ print_method(output, context, total_duration, false)
18
+ end
19
+ output.puts <<-HTML
20
+ <tr class="break">
21
+ <td colspan="7"></td>
22
+ </tr>
23
+ HTML
24
+ end
25
+ output.puts TABLE_FOOTER
26
+ end
27
+ output.puts FOOTER
28
+ end
29
+
30
+ def print_method(output, method, total_duration, major_row)
31
+ return if method.name =~ /JRubyProf\.stop/
32
+ total = method.duration
33
+ total_pc = (total.to_f/total_duration)*100
34
+ children = method.childrens_duration
35
+ self_ = total - children
36
+ self_pc = (self_.to_f/total_duration)*100
37
+ calls = method.count
38
+ name = method.name
39
+ inv_id = nil
40
+ template = File.read(File.join(File.dirname(__FILE__), "..", "..", "templates", "graph_row.html.erb"))
41
+ erb = ERB.new(template)
42
+ output.puts(erb.result(binding))
43
+ end
44
+
45
+ def safe_name(name)
46
+ name.gsub("#", "_inst_").gsub(".", "_stat_")
47
+ end
48
+
49
+ HEADER = <<HTML
50
+ <html>
51
+ <body>
52
+ <head>
53
+ <style media="all" type="text/css">
54
+ table {
55
+ border-collapse: collapse;
56
+ border: 1px solid #CCC;
57
+ font-family: Verdana, Arial, Helvetica, sans-serif;
58
+ font-size: 9pt;
59
+ line-height: normal;
60
+ }
61
+
62
+ th {
63
+ text-align: center;
64
+ border-top: 1px solid #FB7A31;
65
+ border-bottom: 1px solid #FB7A31;
66
+ background: #FFC;
67
+ padding: 0.3em;
68
+ border-left: 1px solid silver;
69
+ }
70
+
71
+ tr.break td {
72
+ border: 0;
73
+ border-top: 1px solid #FB7A31;
74
+ padding: 0;
75
+ margin: 0;
76
+ }
77
+
78
+ tr.method td {
79
+ font-weight: bold;
80
+ }
81
+
82
+ td {
83
+ padding: 0.3em;
84
+ }
85
+
86
+ td:first-child {
87
+ width: 190px;
88
+ }
89
+
90
+ td {
91
+ border-left: 1px solid #CCC;
92
+ text-align: center;
93
+ }
94
+ </style>
95
+ </head>
96
+ HTML
97
+
98
+ TABLE_HEADER = <<-HTML
99
+ <table>
100
+ <tr>
101
+ <th>%total</th>
102
+ <th>%self</th>
103
+ <th>total</th>
104
+ <th>self</th>
105
+ <th>children</th>
106
+ <th>calls</th>
107
+ <th>Name</th>
108
+ </tr>
109
+ HTML
110
+
111
+ TABLE_FOOTER = <<HTML
112
+ </table>
113
+ <br />
114
+ <br />
115
+ HTML
116
+
117
+ FOOTER = <<-HTML
118
+ </body>
119
+ </html>
120
+ HTML
121
+
122
+ end
123
+ end
@@ -0,0 +1,45 @@
1
+
2
+ class JRubyProf
3
+ class GraphTextPrinter < AbstractPrinter
4
+ TABLE_HEADER = " %total %self total self children calls Name"
5
+
6
+ def print_on(output)
7
+ thread_set.invocations.each_with_index do |invocation, i|
8
+ output.puts
9
+ output.puts "Thread #{i + 1} / #{thread_set.length}"
10
+ output.puts
11
+ methods = invocation.get_methods.values.sort_by {|m| m.duration }.reverse
12
+ output.puts TABLE_HEADER
13
+ output.puts "-"*100
14
+ total_duration = thread_set.duration
15
+ rows = methods.map do |method|
16
+ method.parent_contexts.each do |context|
17
+ print_method(output, context, total_duration, false)
18
+ end
19
+ print_method(output, method, total_duration, true)
20
+ method.child_contexts.each do |context|
21
+ print_method(output, context, total_duration, false)
22
+ end
23
+ output.puts "-"*100
24
+ end
25
+ end
26
+ end
27
+
28
+ def print_method(output, method, total_duration, print_percents)
29
+ return if method.name =~ /JRubyProf\.stop/
30
+ total = method.duration
31
+ total_pc = (total.to_f/total_duration)*100
32
+ children = method.childrens_duration
33
+ self_ = total - children
34
+ self_pc = (self_.to_f/total_duration)*100
35
+ calls = method.count
36
+ name = method.name
37
+ if print_percents
38
+ output.print " #{("%2.2f" % total_pc).rjust(6)}% #{("%2.2f" % self_pc).rjust(6)}%"
39
+ else
40
+ output.print " "
41
+ end
42
+ output.puts "#{total.to_s.rjust(11)} #{self_.to_s.rjust(9)} #{children.to_s.rjust(11)} #{calls.to_s.rjust(8)} #{name}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+
2
+ class JRubyProf
3
+ class ThreadSet
4
+ attr_reader :invocations, :duration
5
+
6
+ def initialize(invocations, duration)
7
+ @invocations = invocations.map do |inv|
8
+ c = inv
9
+ c = c.parent while c.parent
10
+ c
11
+ end
12
+ @duration = duration
13
+ end
14
+
15
+ def length
16
+ invocations.length
17
+ end
18
+
19
+ def top_level_duration
20
+ invocations.inject(0.0) do |m, inv|
21
+ m + inv.childrens_duration
22
+ end
23
+ end
24
+
25
+ def self.add_methods(h, inv, duration=nil, count=nil)
26
+ return if inv.name =~ /CachingCallSite\.stop_tracing/ or inv.name =~ /JRubyProf\.stop/
27
+ h[inv.name] ||= Method.new(inv.class_name, inv.method_name, inv.static?)
28
+ h[inv.name].add_invocation(inv)
29
+ inv.children.each do |child_inv|
30
+ add_methods(h, child_inv)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,107 @@
1
+
2
+ class JRubyProf
3
+ class Method
4
+ attr_reader :class_name, :method_name, :invocations
5
+
6
+ def initialize(class_name, method_name, static)
7
+ @class_name, @method_name, @static = class_name, method_name, static
8
+ @invocations = []
9
+ end
10
+
11
+ def static?
12
+ @static
13
+ end
14
+
15
+ def toplevel?
16
+ method_name == nil
17
+ end
18
+
19
+ def add_invocation(inv)
20
+ invocations << inv
21
+ end
22
+
23
+ def duration
24
+ if toplevel?
25
+ childrens_duration
26
+ else
27
+ invocations.inject(0) {|m, inv| m + inv.duration}
28
+ end
29
+ end
30
+
31
+ def count
32
+ if toplevel?
33
+ 1
34
+ else
35
+ invocations.inject(0) {|m, inv| m + inv.count}
36
+ end
37
+ end
38
+
39
+ def childrens_duration
40
+ invocations.inject(0) {|m, inv| m + inv.children.inject(0) {|m1, inv1| m1 + inv1.duration }}
41
+ end
42
+
43
+ def name
44
+ "#{class_name}#{static? ? "." : "#"}#{method_name || "toplevel"}"
45
+ end
46
+
47
+ class CallContext
48
+ attr_accessor :name, :duration, :childrens_duration, :count
49
+
50
+ def toplevel?
51
+ @name == "#"
52
+ end
53
+
54
+ def name
55
+ toplevel? ? "#toplevel" : @name
56
+ end
57
+
58
+ def initialize(name)
59
+ @name = name
60
+ @duration, @childrens_duration, @count = 0, 0, 0
61
+ end
62
+
63
+ def duration
64
+ toplevel? ? @childrens_duration : @duration
65
+ end
66
+ end
67
+
68
+ def child_contexts
69
+ @child_contexts ||= begin
70
+ h = {}
71
+ invocations.each do |inv|
72
+ inv.children.each do |inv2|
73
+ h[inv2.name] ||= CallContext.new(inv2.name)
74
+ cc = h[inv2.name]
75
+ cc.duration += inv2.duration
76
+ cc.childrens_duration += inv2.childrens_duration
77
+ cc.count += inv2.count
78
+ end
79
+ end
80
+ h.values
81
+ end
82
+ end
83
+
84
+ def parent_contexts
85
+ @parent_contexts ||= begin
86
+ h = {}
87
+ invocations.each do |inv|
88
+ inv2 = inv.parent
89
+ next unless inv2
90
+ h[inv2.name] ||= CallContext.new(inv2.name)
91
+ cc = h[inv2.name]
92
+ cc.duration += inv.duration
93
+ cc.childrens_duration += inv.childrens_duration
94
+ cc.count += inv.count
95
+ end
96
+ h.values
97
+ end
98
+ end
99
+
100
+ def inspect
101
+ "#<JRubyProf::Method #{class_name} #{method_name}>"
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+
@@ -0,0 +1,43 @@
1
+
2
+ class JRubyProf
3
+ import org.jruby.prof.Invocation
4
+ class Invocation
5
+ alias :method_name :methodName
6
+ alias :class_name :className
7
+
8
+ def name
9
+ "#{class_name}#{static? ? "." : "#"}#{method_name}"
10
+ end
11
+
12
+ def childrens_duration
13
+ children.inject(0) {|m, inv| m + inv.duration}
14
+ end
15
+
16
+ def static?
17
+ false
18
+ end
19
+
20
+ def to_method
21
+ method = Method.new(class_name, method_name, static?)
22
+ method.add_invocation(self)
23
+ method
24
+ end
25
+
26
+ def id
27
+ @id ||= Invocation.new_id
28
+ end
29
+
30
+ def self.new_id
31
+ @id ||= 0
32
+ @id += 1
33
+ @id
34
+ end
35
+
36
+ def get_methods
37
+ h = {}
38
+ ThreadSet.add_methods(h, self)
39
+ h
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,27 @@
1
+
2
+ class JRubyProf
3
+ class SimpleTreePrinter < AbstractPrinter
4
+ def print_on(output)
5
+ thread_set.invocations.each_with_index do |invocation, i|
6
+ output.puts
7
+ output.puts "*** Thread #{i + 1} / #{thread_set.length}"
8
+ output.puts
9
+ dump_from_root(output, invocation)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def dump(f, inv, indent=0)
16
+ f.print(" "*indent)
17
+ f.puts "#{inv.name} - #{inv.duration}"
18
+ inv.children.each {|child_inv| dump(f, child_inv, indent + 2)}
19
+ end
20
+
21
+ def dump_from_root(f, inv)
22
+ current = inv
23
+ current = current.parent while current.parent
24
+ dump(f, current)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,144 @@
1
+
2
+ class JRubyProf
3
+ class TreeHtmlPrinter < AbstractPrinter
4
+ def print_on(output)
5
+ total_duration = thread_set.duration
6
+ output.puts HEADER
7
+ thread_set.invocations.each_with_index do |invocation, i|
8
+ all_invocations = []
9
+ get_invocations(all_invocations, invocation)
10
+ output.puts "<h3>Thread #{i + 1}/#{thread_set.length}</h3>"
11
+ output.puts TABLE_HEADER
12
+ all_invocations = all_invocations.sort_by {|i| m = i.to_method.duration }.reverse
13
+ all_invocations.each do |inv|
14
+ next if inv.name =~ /CachingCallSite\.stop_tracing/
15
+ next if inv.name =~ /JRubyProf\.stop/
16
+ next if inv.duration < 5
17
+ #next if inv.name == "#"
18
+ c = inv
19
+ parents = []
20
+ while c.parent
21
+ c = c.parent
22
+ parents << c
23
+ end
24
+ parents.reverse.each do |parent_inv|
25
+ print_invocation(output, parent_inv, total_duration, false)
26
+ end
27
+ print_invocation(output, inv, total_duration, true)
28
+ inv.children.each do |child_inv|
29
+ next if child_inv.name =~ /CachingCallSite\.stop_tracing/
30
+ print_invocation(output, child_inv, total_duration, false)
31
+ end
32
+ output.puts <<-HTML
33
+ <tr class="break">
34
+ <td colspan="7"></td>
35
+ </tr>
36
+ HTML
37
+ end
38
+ output.puts TABLE_FOOTER
39
+ end
40
+ output.puts FOOTER
41
+ end
42
+
43
+ def get_invocations(arr, invocation)
44
+ arr << invocation
45
+ invocation.children.each do |inv|
46
+ get_invocations(arr, inv)
47
+ end
48
+ end
49
+
50
+ def print_invocation(output, invocation, total_duration, major_row)
51
+ next if invocation.name =~ /JRubyProf\.stop/
52
+ method = invocation.to_method
53
+ total = method.duration
54
+ total_pc = (total.to_f/total_duration)*100
55
+ children = method.childrens_duration
56
+ self_ = total - children
57
+ self_pc = (self_.to_f/total_duration)*100
58
+ calls = method.count
59
+ name = method.name
60
+ inv_id = invocation.id
61
+ template = File.read(File.join(File.dirname(__FILE__), "..", "..", "templates", "graph_row.html.erb"))
62
+ erb = ERB.new(template)
63
+ output.puts(erb.result(binding))
64
+ end
65
+
66
+ def safe_name(name)
67
+ name.gsub("#", "_inst_").gsub(".", "_stat_")
68
+ end
69
+
70
+ HEADER = <<HTML
71
+ <html>
72
+ <body>
73
+ <head>
74
+ <style media="all" type="text/css">
75
+ table {
76
+ border-collapse: collapse;
77
+ border: 1px solid #CCC;
78
+ font-family: Verdana, Arial, Helvetica, sans-serif;
79
+ font-size: 9pt;
80
+ line-height: normal;
81
+ }
82
+
83
+ th {
84
+ text-align: center;
85
+ border-top: 1px solid #339;
86
+ border-bottom: 1px solid #339;
87
+ background: #CDF;
88
+ padding: 0.3em;
89
+ border-left: 1px solid silver;
90
+ }
91
+
92
+ tr.break td {
93
+ border: 0;
94
+ border-top: 1px solid #339;
95
+ padding: 0;
96
+ margin: 0;
97
+ }
98
+
99
+ tr.method td {
100
+ font-weight: bold;
101
+ }
102
+
103
+ td {
104
+ padding: 0.3em;
105
+ }
106
+
107
+ td:first-child {
108
+ width: 190px;
109
+ }
110
+
111
+ td {
112
+ border-left: 1px solid #CCC;
113
+ text-align: center;
114
+ }
115
+ </style>
116
+ </head>
117
+ HTML
118
+
119
+ TABLE_HEADER = <<-HTML
120
+ <table>
121
+ <tr>
122
+ <th>%total</th>
123
+ <th>%self</th>
124
+ <th>total</th>
125
+ <th>self</th>
126
+ <th>children</th>
127
+ <th>calls</th>
128
+ <th>Name</th>
129
+ </tr>
130
+ HTML
131
+
132
+ TABLE_FOOTER = <<HTML
133
+ </table>
134
+ <br />
135
+ <br />
136
+ HTML
137
+
138
+ FOOTER = <<-HTML
139
+ </body>
140
+ </html>
141
+ HTML
142
+
143
+ end
144
+ end
@@ -0,0 +1,14 @@
1
+
2
+ package org.jruby.prof;
3
+
4
+ import java.util.*;
5
+ public class Invocation {
6
+ public String className;
7
+ public String methodName;
8
+ public Invocation parent;
9
+ public boolean returned = false;
10
+ public long duration = 0;
11
+ public long count = 0;
12
+ public long startTimeCurrent;
13
+ public ArrayList<Invocation> children = new ArrayList<Invocation>();
14
+ }
@@ -0,0 +1,94 @@
1
+
2
+ package org.jruby.prof;
3
+
4
+ import java.util.*;
5
+ import org.jruby.Ruby;
6
+ import org.jruby.runtime.ThreadContext;
7
+
8
+ public class JRubyProf {
9
+
10
+ private static Map<ThreadContext, Invocation> currentInvocations;
11
+
12
+ public static ProfEventHook hook = null;
13
+ public static long startedTracingTime;
14
+ public static long lastTracingDuration;
15
+
16
+ public static void startTracing() {
17
+ hook = new ProfEventHook();
18
+ Ruby.getGlobalRuntime().addEventHook(hook);
19
+ currentInvocations = Collections.synchronizedMap(new HashMap<ThreadContext, Invocation>());
20
+ shouldProfile = true;
21
+ startedTracingTime = System.currentTimeMillis();
22
+ }
23
+
24
+ public static Map<ThreadContext, Invocation> stopTracing() {
25
+ shouldProfile = false;
26
+ Ruby.getGlobalRuntime().removeEventHook(hook);
27
+ hook = null;
28
+ lastTracingDuration = System.currentTimeMillis() - startedTracingTime;
29
+ for (ThreadContext context : currentInvocations.keySet()) {
30
+ Invocation inv = currentInvocations.get(context);
31
+ while (inv.parent != null)
32
+ inv = inv.parent;
33
+ currentInvocations.put(context, inv);
34
+ }
35
+ return currentInvocations;
36
+ }
37
+
38
+ public static boolean isRunning() {
39
+ return shouldProfile;
40
+ }
41
+
42
+ private static boolean shouldProfile = false;
43
+ private boolean initProfileMethod = false;
44
+
45
+ public static synchronized void before(ThreadContext context, String className, String methodName) {
46
+ if (!shouldProfile) return;
47
+ Invocation inv = null;
48
+ if (currentInvocations.containsKey(context)) {
49
+ Invocation parent = currentInvocations.get(context);
50
+ for (Invocation subinv : parent.children) {
51
+ if (subinv.className.equals(className) && subinv.methodName.equals(methodName)) {
52
+ inv = subinv;
53
+ }
54
+ }
55
+ if (inv == null) {
56
+ inv = new Invocation();
57
+ inv.parent = parent;
58
+ inv.className = className;
59
+ inv.methodName = methodName;
60
+ parent.children.add(inv);
61
+ }
62
+ currentInvocations.put(context, inv);
63
+ //System.out.printf("pushing %s %s to %s %s in %s\n", inv.className, inv.methodName, parent.className, parent.methodName, context.toString());
64
+ //System.out.printf("current for %s is %s %s\n", context.toString(), inv.className, inv.methodName);
65
+ }
66
+ else {
67
+ inv = new Invocation();
68
+ currentInvocations.put(context, inv);
69
+ //System.out.printf("current for %s is %s %s\n", context.toString(), inv.className, inv.methodName);
70
+ before(context, className, methodName);
71
+ }
72
+ inv.startTimeCurrent = System.currentTimeMillis();
73
+ }
74
+
75
+ public static synchronized void after(ThreadContext context, String className, String methodName) {
76
+ if (!shouldProfile) return;
77
+ Invocation current = currentInvocations.get(context);
78
+ long time;
79
+ if (current == null) return;
80
+ time = System.currentTimeMillis() - current.startTimeCurrent;
81
+ if (current.startTimeCurrent == 0)
82
+ System.out.printf("warning, startTimeCurrent is 0 in after\n");
83
+ current.startTimeCurrent = 0;
84
+ current.duration += time;
85
+ current.returned = true;
86
+ current.count += 1;
87
+ if (current.parent != null) {
88
+ currentInvocations.put(context, current.parent);
89
+ //System.out.printf("popping: %s %s took %dms, now at %s %s\n", current.className, current.methodName, time, current.parent.className, current.parent.methodName);
90
+ }
91
+ //System.out.printf("current for %s is %s %s\n", context.toString(), current.parent.className, current.parent.methodName);
92
+ }
93
+
94
+ }
@@ -0,0 +1,49 @@
1
+
2
+ package org.jruby.prof;
3
+
4
+ import org.jruby.prof.JRubyProf;
5
+
6
+ import org.jruby.RubyObject;
7
+ import org.jruby.RubyModule;
8
+ import org.jruby.MetaClass;
9
+ import org.jruby.runtime.EventHook;
10
+ import org.jruby.runtime.RubyEvent;
11
+ import org.jruby.runtime.ThreadContext;
12
+ import org.jruby.runtime.builtin.IRubyObject;
13
+
14
+ public class ProfEventHook extends EventHook {
15
+ public void eventHandler(ThreadContext context, String eventName, String file, int line, String methodName, IRubyObject type) {
16
+ RubyModule module = (RubyModule) type;
17
+ String className;
18
+ if (module == null) {
19
+ className = "null";
20
+ }
21
+ else {
22
+ if (module instanceof MetaClass) {
23
+ IRubyObject obj = ((MetaClass) module).getAttached();
24
+ if (obj instanceof RubyModule) {
25
+ module = (RubyModule) obj;
26
+ className = "<Class::" + module.getName() + ">";
27
+ }
28
+ else if (obj instanceof RubyObject) {
29
+ className = "<Instance::" + ((RubyObject) obj).getType().getName() + ">";
30
+ }
31
+ else {
32
+ className = "unknown";
33
+ }
34
+ }
35
+ else {
36
+ className = module.getName();
37
+ }
38
+ }
39
+ //System.out.printf("eventHandler(_, %s, %s, %d, %s, %s)\n", eventName, file, line, methodName, className);
40
+ if (className.equals("<Class::Java::OrgJrubyProf::JRubyProf>")) return;
41
+ if (eventName.equals("call") || eventName.equals("c-call")) {
42
+ JRubyProf.before(context, className, methodName);
43
+ }
44
+ else if (eventName.equals("return") || eventName.equals("c-return")) {
45
+ JRubyProf.after(context, className, methodName);
46
+ }
47
+ }
48
+ public boolean isInterestedInEvent(RubyEvent event) { return true; }
49
+ }
@@ -0,0 +1,18 @@
1
+
2
+
3
+ <tr class="<%= major_row ? "method" : "" %>">
4
+ <td><%= ("%2.2f%" % total_pc) if major_row %></td>
5
+ <td><%= ("%2.2f%" % self_pc) if major_row %></td>
6
+ <td><%= total/1000.0 %></td>
7
+ <td><%= self_/1000.0 %></td>
8
+ <td><%= children/1000.0 %></td>
9
+ <td><%= calls %></td>
10
+ <td>
11
+ <% if major_row %>
12
+ <a name="<%= safe_name(name) + (inv_id ? "_#{inv_id}" : "") %>"><%= name %></a>
13
+ <% else %>
14
+ <a href="#<%= safe_name(name) + (inv_id ? "_#{inv_id}" : "") %>"><%= name %></a>
15
+ <% end %>
16
+ </td>
17
+ </tr>
18
+
@@ -0,0 +1,291 @@
1
+ require 'test/unit'
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
+ require 'jruby-prof'
5
+
6
+ class C1
7
+ def C1.hello
8
+ sleep(0.1)
9
+ end
10
+
11
+ def hello
12
+ sleep(0.2)
13
+ end
14
+ end
15
+
16
+ module M1
17
+ def hello
18
+ sleep(0.3)
19
+ end
20
+ end
21
+
22
+ class C2
23
+ include M1
24
+ extend M1
25
+ end
26
+
27
+ class C3
28
+ def hello
29
+ sleep(0.4)
30
+ end
31
+ end
32
+
33
+ module M4
34
+ def hello
35
+ sleep(0.5)
36
+ end
37
+ end
38
+
39
+ module M5
40
+ include M4
41
+ def goodbye
42
+ hello
43
+ end
44
+ end
45
+
46
+ class C6
47
+ include M5
48
+ def test
49
+ goodbye
50
+ end
51
+ end
52
+
53
+ class BasicTest < Test::Unit::TestCase
54
+ def setup
55
+ # Need to use wall time for this test due to the sleep calls
56
+ #RubyProf::measure_mode = RubyProf::WALL_TIME
57
+ end
58
+
59
+ def test_running
60
+ assert(!JRubyProf.running?)
61
+ JRubyProf.start
62
+ assert(JRubyProf.running?)
63
+ JRubyProf.stop
64
+ assert(!JRubyProf.running?)
65
+ end
66
+
67
+ def test_double_profile
68
+ JRubyProf.start
69
+ assert_raise(RuntimeError) do
70
+ JRubyProf.start
71
+ end
72
+
73
+ assert_raise(RuntimeError) do
74
+ JRubyProf.profile do
75
+ puts 1
76
+ end
77
+ end
78
+ JRubyProf.stop
79
+ end
80
+
81
+ def test_no_block
82
+ assert_raise(ArgumentError) do
83
+ JRubyProf.profile
84
+ end
85
+ end
86
+
87
+ def test_class_methods
88
+ result = JRubyProf.profile do
89
+ C1.hello
90
+ end
91
+
92
+ # Length should be 3:
93
+ # BasicTest#test_class_methods
94
+ # <Class::C1>#hello
95
+ # Kernel#sleep
96
+
97
+ methods = result.values.to_a.first.get_methods.values.sort_by {|m| m.name}
98
+ p methods
99
+ assert_equal(3, methods.length)
100
+
101
+ # Check the names
102
+ assert_equal('BasicTest#test_class_methods', methods[0].name)
103
+ assert_equal('<Class::C1>#hello', methods[1].name)
104
+ assert_equal('Kernel#sleep', methods[2].name)
105
+
106
+ # Check times
107
+ assert_in_delta(0.1, methods[0].total_time, 0.01)
108
+ assert_in_delta(0, methods[0].wait_time, 0.01)
109
+ assert_in_delta(0, methods[0].self_time, 0.01)
110
+
111
+ assert_in_delta(0.1, methods[1].total_time, 0.01)
112
+ assert_in_delta(0, methods[1].wait_time, 0.01)
113
+ assert_in_delta(0, methods[1].self_time, 0.01)
114
+
115
+ assert_in_delta(0.1, methods[2].total_time, 0.01)
116
+ assert_in_delta(0, methods[2].wait_time, 0.01)
117
+ assert_in_delta(0.1, methods[2].self_time, 0.01)
118
+ end
119
+ #
120
+ #if RUBY_VERSION < '1.9'
121
+ # PARENT = Object
122
+ #else
123
+ # PARENT = BasicObject
124
+ #end
125
+ #
126
+ #def test_instance_methods
127
+ # result = RubyProf.profile do
128
+ # C1.new.hello
129
+ # end
130
+ #
131
+ # # Methods called
132
+ # # BasicTest#test_instance_methods
133
+ # # Class.new
134
+ # # Class:Object#allocate
135
+ # # for Object#initialize
136
+ # # C1#hello
137
+ # # Kernel#sleep
138
+ #
139
+ # methods = result.threads.values.first.sort.reverse
140
+ # assert_equal(6, methods.length)
141
+ # names = methods.map(&:full_name)
142
+ # assert_equal('BasicTest#test_instance_methods', names[0])
143
+ # assert_equal('C1#hello', names[1])
144
+ # assert_equal('Kernel#sleep', names[2])
145
+ # assert_equal('Class#new', names[3])
146
+ # # order can differ
147
+ # assert(names.include?("<Class::#{PARENT}>#allocate"))
148
+ # assert(names.include?("#{PARENT}#initialize"))
149
+ #
150
+ # # Check times
151
+ # assert_in_delta(0.2, methods[0].total_time, 0.02)
152
+ # assert_in_delta(0, methods[0].wait_time, 0.02)
153
+ # assert_in_delta(0, methods[0].self_time, 0.02)
154
+ #
155
+ # assert_in_delta(0.2, methods[1].total_time, 0.02)
156
+ # assert_in_delta(0, methods[1].wait_time, 0.02)
157
+ # assert_in_delta(0, methods[1].self_time, 0.02)
158
+ #
159
+ # assert_in_delta(0.2, methods[2].total_time, 0.02)
160
+ # assert_in_delta(0, methods[2].wait_time, 0.02)
161
+ # assert_in_delta(0.2, methods[2].self_time, 0.02)
162
+ #
163
+ # assert_in_delta(0, methods[3].total_time, 0.01)
164
+ # assert_in_delta(0, methods[3].wait_time, 0.01)
165
+ # assert_in_delta(0, methods[3].self_time, 0.01)
166
+ #
167
+ # assert_in_delta(0, methods[4].total_time, 0.01)
168
+ # assert_in_delta(0, methods[4].wait_time, 0.01)
169
+ # assert_in_delta(0, methods[4].self_time, 0.01)
170
+ #
171
+ # assert_in_delta(0, methods[5].total_time, 0.01)
172
+ # assert_in_delta(0, methods[5].wait_time, 0.01)
173
+ # assert_in_delta(0, methods[5].self_time, 0.01)
174
+ #end
175
+ #
176
+ #def test_module_methods
177
+ # result = RubyProf.profile do
178
+ # C2.hello
179
+ # end
180
+ #
181
+ # # Methods:
182
+ # # BasicTest#test_module_methods
183
+ # # M1#hello
184
+ # # Kernel#sleep
185
+ #
186
+ # methods = result.threads.values.first.sort.reverse
187
+ # assert_equal(3, methods.length)
188
+ #
189
+ # assert_equal('BasicTest#test_module_methods', methods[0].full_name)
190
+ # assert_equal('M1#hello', methods[1].full_name)
191
+ # assert_equal('Kernel#sleep', methods[2].full_name)
192
+ #
193
+ # # Check times
194
+ # assert_in_delta(0.3, methods[0].total_time, 0.1)
195
+ # assert_in_delta(0, methods[0].wait_time, 0.02)
196
+ # assert_in_delta(0, methods[0].self_time, 0.02)
197
+ #
198
+ # assert_in_delta(0.3, methods[1].total_time, 0.1)
199
+ # assert_in_delta(0, methods[1].wait_time, 0.02)
200
+ # assert_in_delta(0, methods[1].self_time, 0.02)
201
+ #
202
+ # assert_in_delta(0.3, methods[2].total_time, 0.1)
203
+ # assert_in_delta(0, methods[2].wait_time, 0.02)
204
+ # assert_in_delta(0.3, methods[2].self_time, 0.1)
205
+ #end
206
+ #
207
+ #def test_module_instance_methods
208
+ # result = RubyProf.profile do
209
+ # C2.new.hello
210
+ # end
211
+ #
212
+ # # Methods:
213
+ # # BasicTest#test_module_instance_methods
214
+ # # Class#new
215
+ # # <Class::Object>#allocate
216
+ # # Object#initialize
217
+ # # M1#hello
218
+ # # Kernel#sleep
219
+ #
220
+ # methods = result.threads.values.first.sort.reverse
221
+ # assert_equal(6, methods.length)
222
+ # names = methods.map(&:full_name)
223
+ # assert_equal('BasicTest#test_module_instance_methods', names[0])
224
+ # assert_equal('M1#hello', names[1])
225
+ # assert_equal('Kernel#sleep', names[2])
226
+ # assert_equal('Class#new', names[3])
227
+ # assert(names.include?("<Class::#{PARENT}>#allocate"))
228
+ # assert(names.include?("#{PARENT}#initialize"))
229
+ #
230
+ # # Check times
231
+ # assert_in_delta(0.3, methods[0].total_time, 0.1)
232
+ # assert_in_delta(0, methods[0].wait_time, 0.1)
233
+ # assert_in_delta(0, methods[0].self_time, 0.1)
234
+ #
235
+ # assert_in_delta(0.3, methods[1].total_time, 0.02)
236
+ # assert_in_delta(0, methods[1].wait_time, 0.01)
237
+ # assert_in_delta(0, methods[1].self_time, 0.01)
238
+ #
239
+ # assert_in_delta(0.3, methods[2].total_time, 0.02)
240
+ # assert_in_delta(0, methods[2].wait_time, 0.01)
241
+ # assert_in_delta(0.3, methods[2].self_time, 0.02)
242
+ #
243
+ # assert_in_delta(0, methods[3].total_time, 0.01)
244
+ # assert_in_delta(0, methods[3].wait_time, 0.01)
245
+ # assert_in_delta(0, methods[3].self_time, 0.01)
246
+ #
247
+ # assert_in_delta(0, methods[4].total_time, 0.01)
248
+ # assert_in_delta(0, methods[4].wait_time, 0.01)
249
+ # assert_in_delta(0, methods[4].self_time, 0.01)
250
+ #
251
+ # assert_in_delta(0, methods[5].total_time, 0.01)
252
+ # assert_in_delta(0, methods[5].wait_time, 0.01)
253
+ # assert_in_delta(0, methods[5].self_time, 0.01)
254
+ #end
255
+ #
256
+ #def test_singleton
257
+ # c3 = C3.new
258
+ #
259
+ # class << c3
260
+ # def hello
261
+ # end
262
+ # end
263
+ #
264
+ # result = RubyProf.profile do
265
+ # c3.hello
266
+ # end
267
+ #
268
+ # methods = result.threads.values.first.sort.reverse
269
+ # assert_equal(2, methods.length)
270
+ #
271
+ # assert_equal('BasicTest#test_singleton', methods[0].full_name)
272
+ # assert_equal('<Object::C3>#hello', methods[1].full_name)
273
+ #
274
+ # assert_in_delta(0, methods[0].total_time, 0.01)
275
+ # assert_in_delta(0, methods[0].wait_time, 0.01)
276
+ # assert_in_delta(0, methods[0].self_time, 0.01)
277
+ #
278
+ # assert_in_delta(0, methods[1].total_time, 0.01)
279
+ # assert_in_delta(0, methods[1].wait_time, 0.01)
280
+ # assert_in_delta(0, methods[1].self_time, 0.01)
281
+ #end
282
+ #
283
+ #def test_traceback
284
+ # RubyProf.start
285
+ # assert_raise(NoMethodError) do
286
+ # RubyProf.xxx
287
+ # end
288
+ #
289
+ # RubyProf.stop
290
+ #end
291
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdp-jruby-prof
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 0.1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Lucraft
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-06 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: dan@fluentradical.com
24
+ executables:
25
+ - jruby-prof
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - README
32
+ - lib/jruby-prof/abstract_printer.rb
33
+ - lib/jruby-prof/flat_text_printer.rb
34
+ - lib/jruby-prof/graph_html_printer.rb
35
+ - lib/jruby-prof/graph_text_printer.rb
36
+ - lib/jruby-prof/invocation_set.rb
37
+ - lib/jruby-prof/method.rb
38
+ - lib/jruby-prof/profile_invocation.rb
39
+ - lib/jruby-prof/simple_tree_printer.rb
40
+ - lib/jruby-prof/tree_html_printer.rb
41
+ - lib/jruby-prof.jar
42
+ - lib/jruby-prof.rb
43
+ - templates/graph_row.html.erb
44
+ - src/org/jruby/prof/Invocation.java
45
+ - src/org/jruby/prof/JRubyProf.java
46
+ - src/org/jruby/prof/ProfEventHook.java
47
+ - test/basic_test.rb
48
+ - bin/jruby-prof
49
+ has_rdoc: true
50
+ homepage: http://danlucraft.com/blog
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.7
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: A Ruby level profiler for JRuby
81
+ test_files: []
82
+