ruby-prof 1.0.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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +523 -0
  3. data/LICENSE +25 -0
  4. data/README.rdoc +5 -0
  5. data/Rakefile +110 -0
  6. data/bin/ruby-prof +380 -0
  7. data/bin/ruby-prof-check-trace +45 -0
  8. data/ext/ruby_prof/extconf.rb +36 -0
  9. data/ext/ruby_prof/rp_allocation.c +292 -0
  10. data/ext/ruby_prof/rp_allocation.h +31 -0
  11. data/ext/ruby_prof/rp_call_info.c +283 -0
  12. data/ext/ruby_prof/rp_call_info.h +35 -0
  13. data/ext/ruby_prof/rp_measure_allocations.c +52 -0
  14. data/ext/ruby_prof/rp_measure_memory.c +42 -0
  15. data/ext/ruby_prof/rp_measure_process_time.c +63 -0
  16. data/ext/ruby_prof/rp_measure_wall_time.c +62 -0
  17. data/ext/ruby_prof/rp_measurement.c +236 -0
  18. data/ext/ruby_prof/rp_measurement.h +49 -0
  19. data/ext/ruby_prof/rp_method.c +642 -0
  20. data/ext/ruby_prof/rp_method.h +70 -0
  21. data/ext/ruby_prof/rp_profile.c +881 -0
  22. data/ext/ruby_prof/rp_profile.h +36 -0
  23. data/ext/ruby_prof/rp_stack.c +196 -0
  24. data/ext/ruby_prof/rp_stack.h +56 -0
  25. data/ext/ruby_prof/rp_thread.c +338 -0
  26. data/ext/ruby_prof/rp_thread.h +36 -0
  27. data/ext/ruby_prof/ruby_prof.c +48 -0
  28. data/ext/ruby_prof/ruby_prof.h +17 -0
  29. data/ext/ruby_prof/vc/ruby_prof.sln +31 -0
  30. data/ext/ruby_prof/vc/ruby_prof.vcxproj +143 -0
  31. data/lib/ruby-prof.rb +53 -0
  32. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  33. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  34. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  35. data/lib/ruby-prof/assets/graph_printer.html.erb +356 -0
  36. data/lib/ruby-prof/call_info.rb +57 -0
  37. data/lib/ruby-prof/call_info_visitor.rb +38 -0
  38. data/lib/ruby-prof/compatibility.rb +109 -0
  39. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  40. data/lib/ruby-prof/measurement.rb +14 -0
  41. data/lib/ruby-prof/method_info.rb +90 -0
  42. data/lib/ruby-prof/printers/abstract_printer.rb +118 -0
  43. data/lib/ruby-prof/printers/call_info_printer.rb +51 -0
  44. data/lib/ruby-prof/printers/call_stack_printer.rb +269 -0
  45. data/lib/ruby-prof/printers/call_tree_printer.rb +151 -0
  46. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  47. data/lib/ruby-prof/printers/flat_printer.rb +52 -0
  48. data/lib/ruby-prof/printers/graph_html_printer.rb +64 -0
  49. data/lib/ruby-prof/printers/graph_printer.rb +114 -0
  50. data/lib/ruby-prof/printers/multi_printer.rb +127 -0
  51. data/lib/ruby-prof/profile.rb +33 -0
  52. data/lib/ruby-prof/rack.rb +171 -0
  53. data/lib/ruby-prof/task.rb +147 -0
  54. data/lib/ruby-prof/thread.rb +35 -0
  55. data/lib/ruby-prof/version.rb +3 -0
  56. data/lib/unprof.rb +10 -0
  57. data/ruby-prof.gemspec +58 -0
  58. data/test/abstract_printer_test.rb +26 -0
  59. data/test/alias_test.rb +129 -0
  60. data/test/basic_test.rb +129 -0
  61. data/test/call_info_visitor_test.rb +31 -0
  62. data/test/duplicate_names_test.rb +32 -0
  63. data/test/dynamic_method_test.rb +53 -0
  64. data/test/enumerable_test.rb +21 -0
  65. data/test/exceptions_test.rb +24 -0
  66. data/test/exclude_methods_test.rb +146 -0
  67. data/test/exclude_threads_test.rb +53 -0
  68. data/test/line_number_test.rb +161 -0
  69. data/test/marshal_test.rb +119 -0
  70. data/test/measure_allocations.rb +30 -0
  71. data/test/measure_allocations_test.rb +385 -0
  72. data/test/measure_allocations_trace_test.rb +385 -0
  73. data/test/measure_memory_trace_test.rb +756 -0
  74. data/test/measure_process_time_test.rb +849 -0
  75. data/test/measure_times.rb +54 -0
  76. data/test/measure_wall_time_test.rb +459 -0
  77. data/test/multi_printer_test.rb +71 -0
  78. data/test/no_method_class_test.rb +15 -0
  79. data/test/parser_timings.rb +24 -0
  80. data/test/pause_resume_test.rb +166 -0
  81. data/test/prime.rb +56 -0
  82. data/test/printer_call_tree_test.rb +31 -0
  83. data/test/printer_flat_test.rb +68 -0
  84. data/test/printer_graph_html_test.rb +60 -0
  85. data/test/printer_graph_test.rb +41 -0
  86. data/test/printers_test.rb +141 -0
  87. data/test/printing_recursive_graph_test.rb +81 -0
  88. data/test/rack_test.rb +157 -0
  89. data/test/recursive_test.rb +210 -0
  90. data/test/singleton_test.rb +38 -0
  91. data/test/stack_printer_test.rb +64 -0
  92. data/test/start_stop_test.rb +109 -0
  93. data/test/test_helper.rb +24 -0
  94. data/test/thread_test.rb +144 -0
  95. data/test/unique_call_path_test.rb +190 -0
  96. data/test/yarv_test.rb +56 -0
  97. metadata +189 -0
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ # Test data
7
+ # A
8
+ # / \
9
+ # B C
10
+ # \
11
+ # B
12
+
13
+ class MSTPT
14
+ def a
15
+ 100.times{b}
16
+ 300.times{c}
17
+ c;c;c
18
+ end
19
+
20
+ def b
21
+ sleep 0
22
+ end
23
+
24
+ def c
25
+ 5.times{b}
26
+ end
27
+ end
28
+
29
+ class MultiPrinterTest < TestCase
30
+ def setup
31
+ # Need to use wall time for this test due to the sleep calls
32
+ RubyProf::measure_mode = RubyProf::WALL_TIME
33
+ end
34
+
35
+ def test_refuses_io_objects
36
+ # we don't need a real profile for this test
37
+ p = RubyProf::MultiPrinter.new nil
38
+ begin
39
+ p.print(STDOUT)
40
+ flunk "should have raised an ArgumentError"
41
+ rescue ArgumentError => e
42
+ assert_match(/IO/, e.to_s)
43
+ end
44
+ end
45
+
46
+ def test_refuses_non_hashes
47
+ # we don't need a real profile for this test
48
+ p = RubyProf::MultiPrinter.new nil
49
+ begin
50
+ p.print([])
51
+ flunk "should have raised an ArgumentError"
52
+ rescue ArgumentError => e
53
+ assert_match(/hash/, e.to_s)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def print(result)
60
+ test = caller.first =~ /in `(.*)'/ ? $1 : "test"
61
+ path = Dir.tmpdir
62
+ profile = "ruby_prof_#{test}"
63
+ printer = RubyProf::MultiPrinter.new(result)
64
+ printer.print(:path => path, :profile => profile,
65
+ :threshold => 0, :min_percent => 0, :title => "ruby_prof #{test}")
66
+ if RUBY_PLATFORM =~ /darwin/ && ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT']=="1"
67
+ system("open '#{printer.stack_profile}'")
68
+ end
69
+ [File.read(printer.stack_profile), File.read(printer.graph_profile)]
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ # Make sure this works with no class or method
7
+ result = RubyProf.profile do
8
+ sleep 1
9
+ end
10
+
11
+ methods = result.threads.first.methods
12
+ global_method = methods.sort_by {|method| method.full_name}.first
13
+ if global_method.full_name != 'Kernel#sleep'
14
+ raise(RuntimeError, "Wrong method name. Expected: Global#[No method]. Actual: #{global_method.full_name}")
15
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ require 'ruby-prof'
4
+
5
+ require 'parser/current'
6
+ Parser::Builders::Default.emit_lambda = true
7
+ Parser::Builders::Default.emit_procarg0 = true
8
+ Parser::Builders::Default.emit_encoding = true
9
+ Parser::Builders::Default.emit_index = true
10
+
11
+ start = Time.now
12
+ result = RubyProf.profile(:measure_mode => RubyProf::ALLOCATIONS, :track_allocations => true) do
13
+ code = File.read('C:/msys64/usr/local/src/ruby-2.6.3/lib/rdoc/markdown.rb')
14
+ parse = Parser::CurrentRuby.parse(code)
15
+ end
16
+
17
+ finish = Time.now
18
+
19
+ puts "#{finish - start} seconds"
20
+
21
+ printer = RubyProf::GraphHtmlPrinter.new(result)
22
+ File.open('c:/temp/graph.html', 'wb') do |file|
23
+ printer.print(file)
24
+ end
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ class PauseResumeTest < TestCase
7
+ def setup
8
+ # Need to use wall time for this test due to the sleep calls
9
+ RubyProf::measure_mode = RubyProf::WALL_TIME
10
+ end
11
+
12
+ def test_pause_resume
13
+ # Measured
14
+ RubyProf.start
15
+ RubyProf::C1.sleep_wait
16
+
17
+ # Not measured
18
+ RubyProf.pause
19
+ sleep 1
20
+ RubyProf::C1.sleep_wait
21
+
22
+ # Measured
23
+ RubyProf.resume
24
+ RubyProf::C1.sleep_wait
25
+
26
+ result = RubyProf.stop
27
+
28
+ # Length should be 3:
29
+ # PauseResumeTest#test_pause_resume
30
+ # <Class::RubyProf::C1>#sleep_wait
31
+ # Kernel#sleep
32
+
33
+ methods = result.threads.first.methods.sort_by {|method_info| method_info.full_name}
34
+ # remove methods called by pause/resume
35
+ called_methods = ['Array#include?', 'Fixnum#==', 'Kernel#respond_to?', 'Kernel#respond_to_missing?']
36
+ methods.reject!{|m| called_methods.include?(m.full_name) }
37
+ # TODO: fix pause/resume to not include those methods in the first place
38
+ assert_equal(3, methods.length)
39
+
40
+ # Check the names
41
+ assert_equal('<Class::RubyProf::C1>#sleep_wait', methods[0].full_name)
42
+ assert_equal('Kernel#sleep', methods[1].full_name)
43
+ assert_equal('PauseResumeTest#test_pause_resume', methods[2].full_name)
44
+
45
+ # Check times
46
+ assert_in_delta(0.2, methods[0].total_time, 0.02)
47
+ assert_in_delta(0, methods[0].wait_time, 0.02)
48
+ assert_in_delta(0, methods[0].self_time, 0.02)
49
+
50
+ assert_in_delta(0.2, methods[1].total_time, 0.02)
51
+ assert_in_delta(0, methods[1].wait_time, 0.02)
52
+ assert_in_delta(0.2, methods[1].self_time, 0.02)
53
+
54
+ assert_in_delta(0.2, methods[2].total_time, 0.02)
55
+ assert_in_delta(0, methods[2].wait_time, 0.02)
56
+ assert_in_delta(0, methods[2].self_time, 0.02)
57
+ end
58
+
59
+ # pause/resume in the same frame
60
+ def test_pause_resume_1
61
+ profile = RubyProf::Profile.new
62
+
63
+ profile.start
64
+ method_1a
65
+
66
+ profile.pause
67
+ method_1b
68
+
69
+ profile.resume
70
+ method_1c
71
+
72
+ result = profile.stop
73
+ assert_in_delta(0.6, result.threads[0].methods.select{|m| m.full_name =~ /test_pause_resume_1$/}[0].total_time, 0.05)
74
+ end
75
+ def method_1a; sleep 0.2 end
76
+ def method_1b; sleep 1 end
77
+ def method_1c; sleep 0.4 end
78
+
79
+ # pause in parent frame, resume in child
80
+ def test_pause_resume_2
81
+ profile = RubyProf::Profile.new
82
+
83
+ profile.start
84
+ method_2a
85
+
86
+ profile.pause
87
+ sleep 0.5
88
+ method_2b(profile)
89
+
90
+ result = profile.stop
91
+ assert_in_delta(0.6, result.threads[0].methods.select{|m| m.full_name =~ /test_pause_resume_2$/}[0].total_time, 0.05)
92
+ end
93
+ def method_2a; sleep 0.2 end
94
+ def method_2b(profile); sleep 0.5; profile.resume; sleep 0.4 end
95
+
96
+ # pause in child frame, resume in parent
97
+ def test_pause_resume_3
98
+ profile = RubyProf::Profile.new
99
+
100
+ profile.start
101
+ method_3a(profile)
102
+
103
+ sleep 0.5
104
+ profile.resume
105
+ method_3b
106
+
107
+ result = profile.stop
108
+ assert_in_delta(0.6, result.threads[0].methods.select{|m| m.full_name =~ /test_pause_resume_3$/}[0].total_time, 0.05)
109
+ end
110
+ def method_3a(profile); sleep 0.2; profile.pause; sleep 0.5 end
111
+ def method_3b; sleep 0.4 end
112
+
113
+ def test_pause_seq
114
+ profile = RubyProf::Profile.new
115
+ profile.start ; assert !profile.paused?
116
+ profile.pause ; assert profile.paused?
117
+ profile.resume; assert !profile.paused?
118
+ profile.pause ; assert profile.paused?
119
+ profile.pause ; assert profile.paused?
120
+ profile.resume; assert !profile.paused?
121
+ profile.resume; assert !profile.paused?
122
+ profile.stop ; assert !profile.paused?
123
+ end
124
+
125
+ def test_pause_block
126
+ profile = RubyProf::Profile.new
127
+ profile.start
128
+ profile.pause
129
+ assert profile.paused?
130
+
131
+ times_block_invoked = 0
132
+ retval= profile.resume{
133
+ times_block_invoked += 1
134
+ 120 + times_block_invoked
135
+ }
136
+ assert_equal 1, times_block_invoked
137
+ assert profile.paused?
138
+
139
+ assert_equal 121, retval, "resume() should return the result of the given block."
140
+
141
+ profile.stop
142
+ end
143
+
144
+ def test_pause_block_with_error
145
+ profile = RubyProf::Profile.new
146
+ profile.start
147
+ profile.pause
148
+ assert profile.paused?
149
+
150
+ begin
151
+ profile.resume{ raise }
152
+ flunk 'Exception expected.'
153
+ rescue
154
+ assert profile.paused?
155
+ end
156
+
157
+ profile.stop
158
+ end
159
+
160
+ def test_resume_when_not_paused
161
+ profile = RubyProf::Profile.new
162
+ profile.start ; assert !profile.paused?
163
+ profile.resume; assert !profile.paused?
164
+ profile.stop ; assert !profile.paused?
165
+ end
166
+ end
@@ -0,0 +1,56 @@
1
+ # A silly little test program that finds prime numbers. It
2
+ # is intentionally badly designed to show off the use
3
+ # of ruby-prof.
4
+ #
5
+ # Source from http://people.cs.uchicago.edu/~bomb154/154/maclabs/profilers-lab/
6
+
7
+ require File.expand_path('../test_helper', __FILE__)
8
+
9
+ def make_random_array(length, maxnum)
10
+ result = Array.new(length)
11
+ result.each_index do |i|
12
+ result[i] = rand(maxnum)
13
+ end
14
+
15
+ result
16
+ end
17
+
18
+ def is_prime(x)
19
+ y = 2
20
+ y.upto(x-1) do |i|
21
+ return false if (x % i) == 0
22
+ end
23
+ true
24
+ end
25
+
26
+ def find_primes(arr)
27
+ result = arr.select do |value|
28
+ is_prime(value)
29
+ end
30
+ result
31
+ end
32
+
33
+ def find_largest(primes)
34
+ largest = primes.first
35
+
36
+ # Intentionally use upto for example purposes
37
+ # (upto is also called from is_prime)
38
+ 0.upto(primes.length-1) do |i|
39
+ prime = primes[i]
40
+ if prime > largest
41
+ largest = prime
42
+ end
43
+ end
44
+ largest
45
+ end
46
+
47
+ def run_primes(length=10, maxnum=1000)
48
+ # Create random numbers
49
+ random_array = make_random_array(length, maxnum)
50
+
51
+ # Find the primes
52
+ primes = find_primes(random_array)
53
+
54
+ # Find the largest primes
55
+ find_largest(primes)
56
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+ require 'stringio'
6
+ require 'fileutils'
7
+ require 'tmpdir'
8
+ require_relative 'prime'
9
+
10
+ # -- Tests ----
11
+ class PrinterCallTreeTest < TestCase
12
+ def setup
13
+ # WALL_TIME so we can use sleep in our test and get same measurements on linux and windows
14
+ RubyProf::measure_mode = RubyProf::WALL_TIME
15
+ @result = RubyProf.profile do
16
+ run_primes(1000, 5000)
17
+ end
18
+ end
19
+
20
+ def test_call_tree_string
21
+ printer = RubyProf::CallTreePrinter.new(@result)
22
+
23
+ printer.print(:profile => "lolcat", :path => Dir.tmpdir)
24
+ main_output_file_name = File.join(Dir.tmpdir, "lolcat.callgrind.out.#{$$}")
25
+ assert(File.exist?(main_output_file_name))
26
+ output = File.read(main_output_file_name)
27
+ assert_match(/fn=Object::find_primes/i, output)
28
+ assert_match(/events: wall_time/i, output)
29
+ refute_match(/d\d\d\d\d\d/, output) # old bug looked [in error] like Object::run_primes(d5833116)
30
+ end
31
+ end
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+ require 'stringio'
6
+ require 'fileutils'
7
+ require 'tmpdir'
8
+ require_relative 'prime'
9
+
10
+ # -- Tests ----
11
+ class PrinterFlatTest < TestCase
12
+ def setup
13
+ # WALL_TIME so we can use sleep in our test and get same measurements on linux and windows
14
+ RubyProf::measure_mode = RubyProf::WALL_TIME
15
+ @result = RubyProf.profile do
16
+ run_primes(1000, 5000)
17
+ end
18
+ end
19
+
20
+ def flat_output_nth_column_values(output, n)
21
+ only_method_calls = output.split("\n").select { |line| line =~ /^ +\d+/ }
22
+ only_method_calls.collect { |line| line.split(/ +/)[n] }
23
+ end
24
+
25
+ def assert_sorted array
26
+ array = array.map{|n| n.to_f} # allow for > 10s times to sort right, since lexographically 4.0 > 10.0
27
+ assert_equal array, array.sort.reverse, "Array #{array.inspect} is not sorted"
28
+ end
29
+
30
+ def test_flat_string
31
+ output = helper_test_flat_string(RubyProf::FlatPrinter)
32
+ assert_match(/prime.rb/, output)
33
+ end
34
+
35
+ def helper_test_flat_string(klass)
36
+ output = ''
37
+
38
+ printer = klass.new(@result)
39
+ printer.print(output)
40
+
41
+ assert_match(/Thread ID: -?\d+/i, output)
42
+ assert_match(/Fiber ID: -?\d+/i, output)
43
+ assert_match(/Total: \d+\.\d+/i, output)
44
+ assert_match(/Object#run_primes/i, output)
45
+ output
46
+ end
47
+
48
+ def test_flat_result_sorting_by_self_time_is_default
49
+ printer = RubyProf::FlatPrinter.new(@result)
50
+
51
+ printer.print(output = '')
52
+ self_times = flat_output_nth_column_values(output, 3)
53
+
54
+ assert_sorted self_times
55
+ end
56
+
57
+ def test_flat_result_sorting
58
+ printer = RubyProf::FlatPrinter.new(@result)
59
+
60
+ sort_method_with_column_number = {:total_time => 2, :self_time => 3, :wait_time => 4, :children_time => 5}
61
+
62
+ sort_method_with_column_number.each_pair do |sort_method, n|
63
+ printer.print(output = '', :sort_method => sort_method)
64
+ times = flat_output_nth_column_values(output, n)
65
+ assert_sorted times
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+ require 'stringio'
6
+ require 'fileutils'
7
+ require 'tmpdir'
8
+ require_relative 'prime'
9
+
10
+ # -- Tests ----
11
+ class PrinterGraphHtlmTest < TestCase
12
+ def setup
13
+ # WALL_TIME so we can use sleep in our test and get same measurements on linux and windows
14
+ RubyProf::measure_mode = RubyProf::WALL_TIME
15
+ @result = RubyProf.profile do
16
+ run_primes(1000, 5000)
17
+ end
18
+ end
19
+
20
+ def graph_html_output_nth_column_values(output, n)
21
+ only_root_calls = output.split('<tr class="method">')
22
+ only_root_calls.delete_at(0)
23
+ only_root_calls.collect {|line| line.scan(/[\d\.]+/)[n - 1] }
24
+ end
25
+
26
+ def assert_sorted array
27
+ array = array.map{|n| n.to_f} # allow for > 10s times to sort right, since lexographically 4.0 > 10.0
28
+ assert_equal array, array.sort.reverse, "Array #{array.inspect} is not sorted"
29
+ end
30
+
31
+ def test_graph_html_string
32
+ output = ''
33
+ printer = RubyProf::GraphHtmlPrinter.new(@result)
34
+ printer.print(output)
35
+
36
+ assert_match(/<!DOCTYPE html>/i, output)
37
+ assert_match( %r{<th>Total</th>}i, output)
38
+ assert_match(/Object#run_primes/i, output)
39
+ end
40
+
41
+ def test_graph_html_result_sorting_by_total_time_is_default
42
+ printer = RubyProf::GraphHtmlPrinter.new(@result)
43
+ printer.print(output = '')
44
+ total_times = graph_html_output_nth_column_values(output, 3)
45
+
46
+ assert_sorted total_times
47
+ end
48
+
49
+ def test_graph_html_result_sorting
50
+ printer = RubyProf::GraphHtmlPrinter.new(@result)
51
+
52
+ sort_method_with_column_number = {:total_time => 3, :self_time => 4, :wait_time => 5, :children_time => 6}
53
+
54
+ sort_method_with_column_number.each_pair do |sort_method, n|
55
+ printer.print(output = '', :sort_method => sort_method)
56
+ times = graph_html_output_nth_column_values(output, n)
57
+ assert_sorted times
58
+ end
59
+ end
60
+ end