airbnb-ruby-prof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,65 @@
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 STPT
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 StackPrinterTest < 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_stack_can_be_printed
36
+ start_time = Time.now
37
+ RubyProf.start
38
+ 5.times{STPT.new.a}
39
+ result = RubyProf.stop
40
+ end_time = Time.now
41
+ expected_time = end_time - start_time
42
+
43
+ file_contents = nil
44
+ assert_nothing_raised { file_contents = print(result) }
45
+ # TODO: why are thread ids negative on travis-ci.org (32 bit build maybe?)
46
+ re = /Thread: (-?\d+)(, Fiber: (-?\d+))? \(100\.00% ~ ([\.0-9]+)\)/
47
+ assert_match(re, file_contents)
48
+ file_contents =~ re
49
+ actual_time = $4.to_f
50
+ assert_in_delta(expected_time, actual_time, 0.1)
51
+ end
52
+
53
+ private
54
+ def print(result)
55
+ test = caller.first =~ /in `(.*)'/ ? $1 : "test"
56
+ testfile_name = "#{RubyProf.tmpdir}/ruby_prof_#{test}.html"
57
+ # puts "printing to #{testfile_name}"
58
+ printer = RubyProf::CallStackPrinter.new(result)
59
+ File.open(testfile_name, "w") {|f| printer.print(f, :threshold => 0, :min_percent => 0, :title => "ruby_prof #{test}")}
60
+ system("open '#{testfile_name}'") if RUBY_PLATFORM =~ /darwin/ && ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT']=="1"
61
+ assert File.exist?(testfile_name), "#{testfile_name} does not exist"
62
+ assert File.readable?(testfile_name), "#{testfile_name} is no readable"
63
+ File.read(testfile_name)
64
+ end
65
+ end
@@ -0,0 +1,138 @@
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 StackClass
14
+ def a
15
+ sleep 1
16
+ b
17
+ c
18
+ end
19
+
20
+ def b
21
+ sleep 2
22
+ end
23
+
24
+ def c
25
+ sleep 3
26
+ b
27
+ end
28
+ end
29
+
30
+ class StackTest < TestCase
31
+ def setup
32
+ # Need to use wall time for this test due to the sleep calls
33
+ RubyProf::measure_mode = RubyProf::WALL_TIME
34
+ end
35
+
36
+ def test_call_sequence
37
+ c = StackClass.new
38
+ result = RubyProf.profile do
39
+ c.a
40
+ end
41
+
42
+ # Length should be 5:
43
+ # StackTest#test_call_sequence
44
+ # StackClass#a
45
+ # Kernel#sleep
46
+ # StackClass#c
47
+ # StackClass#b
48
+
49
+ methods = result.threads.first.methods.sort.reverse
50
+ assert_equal(5, methods.length)
51
+
52
+ # Check StackTest#test_call_sequence
53
+ method = methods[0]
54
+ assert_equal('StackTest#test_call_sequence', method.full_name)
55
+ assert_equal(1, method.called)
56
+ assert_in_delta(8, method.total_time, 0.25)
57
+ assert_in_delta(0, method.wait_time, 0.01)
58
+ assert_in_delta(0, method.self_time, 0.01)
59
+ assert_in_delta(8, method.children_time, 0.25)
60
+ assert_equal(1, method.call_infos.length)
61
+
62
+ call_info = method.call_infos[0]
63
+ assert_equal('StackTest#test_call_sequence', call_info.call_sequence)
64
+ assert_equal(1, call_info.children.length)
65
+
66
+ # Check StackClass#a
67
+ method = methods[1]
68
+ assert_equal('StackClass#a', method.full_name)
69
+ assert_equal(1, method.called)
70
+ assert_in_delta(8, method.total_time, 0.15)
71
+ assert_in_delta(0, method.wait_time, 0.01)
72
+ assert_in_delta(0, method.self_time, 0.01)
73
+ assert_in_delta(8, method.children_time, 0.05)
74
+ assert_equal(1, method.call_infos.length)
75
+
76
+ call_info = method.call_infos[0]
77
+ assert_equal('StackTest#test_call_sequence->StackClass#a', call_info.call_sequence)
78
+ assert_equal(3, call_info.children.length)
79
+
80
+ # Check Kernel#sleep
81
+ method = methods[2]
82
+ assert_equal('Kernel#sleep', method.full_name)
83
+ assert_equal(4, method.called)
84
+ assert_in_delta(8, method.total_time, 0.05)
85
+ assert_in_delta(0, method.wait_time, 0.01)
86
+ assert_in_delta(8, method.self_time, 0.05)
87
+ assert_in_delta(0, method.children_time, 0.05)
88
+ assert_equal(4, method.call_infos.length)
89
+
90
+ call_info = method.call_infos[0]
91
+ assert_equal('StackTest#test_call_sequence->StackClass#a->Kernel#sleep', call_info.call_sequence)
92
+ assert_equal(0, call_info.children.length)
93
+
94
+ call_info = method.call_infos[1]
95
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b->Kernel#sleep', call_info.call_sequence)
96
+ assert_equal(0, call_info.children.length)
97
+
98
+ call_info = method.call_infos[2]
99
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->Kernel#sleep', call_info.call_sequence)
100
+ assert_equal(0, call_info.children.length)
101
+
102
+ call_info = method.call_infos[3]
103
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b->Kernel#sleep', call_info.call_sequence)
104
+ assert_equal(0, call_info.children.length)
105
+
106
+ # Check StackClass#c
107
+ method = methods[3]
108
+ assert_equal('StackClass#c', method.full_name)
109
+ assert_equal(1, method.called)
110
+ assert_in_delta(5, method.total_time, 0.05)
111
+ assert_in_delta(0, method.wait_time, 0.01)
112
+ assert_in_delta(0, method.self_time, 0.01)
113
+ assert_in_delta(5, method.children_time, 0.05)
114
+ assert_equal(1, method.call_infos.length)
115
+
116
+ call_info = method.call_infos[0]
117
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c', call_info.call_sequence)
118
+ assert_equal(2, call_info.children.length)
119
+
120
+ # Check StackClass#b
121
+ method = methods[4]
122
+ assert_equal('StackClass#b', method.full_name)
123
+ assert_equal(2, method.called)
124
+ assert_in_delta(4, method.total_time, 0.05)
125
+ assert_in_delta(0, method.wait_time, 0.01)
126
+ assert_in_delta(0, method.self_time, 0.01)
127
+ assert_in_delta(4, method.children_time, 0.05)
128
+ assert_equal(2, method.call_infos.length)
129
+
130
+ call_info = method.call_infos[0]
131
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b', call_info.call_sequence)
132
+ assert_equal(1, call_info.children.length)
133
+
134
+ call_info = method.call_infos[1]
135
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b', call_info.call_sequence)
136
+ assert_equal(1, call_info.children.length)
137
+ end
138
+ end
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ class StartStopTest < 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 method1
13
+ RubyProf.start
14
+ method2
15
+ end
16
+
17
+ def method2
18
+ method3
19
+ end
20
+
21
+ def method3
22
+ sleep(2)
23
+ @result = RubyProf.stop
24
+ end
25
+
26
+ def test_extra_stop_should_raise
27
+ RubyProf.start
28
+ assert_raises(RuntimeError) do
29
+ RubyProf.start
30
+ end
31
+
32
+ assert_raises(RuntimeError) do
33
+ RubyProf.profile {}
34
+ end
35
+
36
+ RubyProf.stop # ok
37
+ assert_raises(RuntimeError) do
38
+ RubyProf.stop
39
+ end
40
+ end
41
+
42
+
43
+ def test_different_methods
44
+ method1
45
+
46
+ # Ruby prof should be stopped
47
+ assert_equal(false, RubyProf.running?)
48
+
49
+
50
+ # Length should be 4:
51
+ # StartStopTest#method1
52
+ # StartStopTest#method2
53
+ # StartStopTest#method3
54
+ # Kernel#sleep
55
+
56
+ methods = @result.threads.first.methods.sort.reverse
57
+ assert_equal(4, methods.length)
58
+
59
+ # Check StackTest#test_call_sequence
60
+ method = methods[0]
61
+ assert_equal('StartStopTest#method1', method.full_name)
62
+ assert_equal(1, method.called)
63
+ assert_in_delta(2, method.total_time, 0.05)
64
+ assert_in_delta(0, method.wait_time, 0.02)
65
+ assert_in_delta(0, method.self_time, 0.02)
66
+ assert_in_delta(2, method.children_time, 0.05)
67
+ assert_equal(1, method.call_infos.length)
68
+
69
+ call_info = method.call_infos[0]
70
+ assert_equal('StartStopTest#method1', call_info.call_sequence)
71
+ assert_equal(1, call_info.children.length)
72
+
73
+ method = methods[1]
74
+ assert_equal('StartStopTest#method2', method.full_name)
75
+ assert_equal(1, method.called)
76
+ assert_in_delta(2, method.total_time, 0.05)
77
+ assert_in_delta(0, method.wait_time, 0.02)
78
+ assert_in_delta(0, method.self_time, 0.02)
79
+ assert_in_delta(2, method.children_time, 0.05)
80
+ assert_equal(1, method.call_infos.length)
81
+
82
+ call_info = method.call_infos[0]
83
+ assert_equal('StartStopTest#method1->StartStopTest#method2', call_info.call_sequence)
84
+ assert_equal(1, call_info.children.length)
85
+
86
+ method = methods[2]
87
+ assert_equal('StartStopTest#method3', method.full_name)
88
+ assert_equal(1, method.called)
89
+ assert_in_delta(2, method.total_time, 0.02)
90
+ assert_in_delta(0, method.wait_time, 0.02)
91
+ assert_in_delta(0, method.self_time, 0.02)
92
+ assert_in_delta(2, method.children_time, 0.02)
93
+ assert_equal(1, method.call_infos.length)
94
+
95
+ call_info = method.call_infos[0]
96
+ assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3', call_info.call_sequence)
97
+ assert_equal(1, call_info.children.length)
98
+
99
+ method = methods[3]
100
+ assert_equal('Kernel#sleep', method.full_name)
101
+ assert_equal(1, method.called)
102
+ assert_in_delta(2, method.total_time, 0.02)
103
+ assert_in_delta(0, method.wait_time, 0.02)
104
+ assert_in_delta(2, method.self_time, 0.02)
105
+ assert_in_delta(0, method.children_time, 0.02)
106
+ assert_equal(1, method.call_infos.length)
107
+
108
+ call_info = method.call_infos[0]
109
+ assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3->Kernel#sleep', call_info.call_sequence)
110
+ assert_equal(0, call_info.children.length)
111
+ end
112
+ end
@@ -0,0 +1,264 @@
1
+ # encoding: UTF-8
2
+
3
+ # Make RubyMine happy
4
+ require "rubygems"
5
+ gem "minitest"
6
+
7
+ if ENV["RM_INFO"] || ENV["TEAMCITY_VERSION"]
8
+ if RUBY_PLATFORM =~ /(win32|mingw)/
9
+ gem "win32console"
10
+ end
11
+ gem "minitest-reporters"
12
+ require 'minitest/reporters'
13
+ MiniTest::Reporters.use!
14
+ end
15
+
16
+ require "minitest/pride" if RUBY_VERSION == "1.9.3"
17
+
18
+ # To make testing/debugging easier, test within this source tree versus an installed gem
19
+ dir = File.dirname(__FILE__)
20
+ root = File.expand_path(File.join(dir, '..'))
21
+ lib = File.expand_path(File.join(root, 'lib'))
22
+ ext = File.expand_path(File.join(root, 'ext', 'ruby_prof'))
23
+
24
+ $LOAD_PATH << lib
25
+ $LOAD_PATH << ext
26
+
27
+ require 'ruby-prof'
28
+ require 'minitest/autorun'
29
+
30
+ class TestCase < Minitest::Test
31
+ # I know this sucks, but ...
32
+ def assert_nothing_raised(*)
33
+ yield
34
+ end
35
+
36
+ def before_setup
37
+ # make sure to exclude all threads except the one running the test
38
+ # minitest allocates a thread pool and they would otherwise show
39
+ # up in the profile data, breaking tests randomly
40
+ RubyProf.exclude_threads = Thread.list.select{|t| t != Thread.current}
41
+ end
42
+
43
+ def after_teardown
44
+ # reset exclude threads after testing
45
+ RubyProf.exclude_threads = nil
46
+ end
47
+ end
48
+
49
+ require File.expand_path('../prime', __FILE__)
50
+
51
+ # Some classes used in measurement tests
52
+ module RubyProf
53
+ class C1
54
+ def C1.hello
55
+ sleep(0.1)
56
+ end
57
+
58
+ def hello
59
+ sleep(0.2)
60
+ end
61
+ end
62
+
63
+ module M1
64
+ def hello
65
+ sleep(0.3)
66
+ end
67
+ end
68
+
69
+ class C2
70
+ include M1
71
+ extend M1
72
+ end
73
+
74
+ class C3
75
+ def hello
76
+ sleep(0.4)
77
+ end
78
+ end
79
+
80
+ module M4
81
+ def hello
82
+ sleep(0.5)
83
+ end
84
+ end
85
+
86
+ module M5
87
+ include M4
88
+ def goodbye
89
+ hello
90
+ end
91
+ end
92
+
93
+ class C6
94
+ include M5
95
+ def test
96
+ goodbye
97
+ end
98
+ end
99
+
100
+ class C7
101
+ def self.busy_wait
102
+ t = Time.now.to_f
103
+ while Time.now.to_f - t < 0.1; end
104
+ end
105
+
106
+ def self.sleep_wait
107
+ sleep 0.1
108
+ end
109
+
110
+ def busy_wait
111
+ t = Time.now.to_f
112
+ while Time.now.to_f - t < 0.2; end
113
+ end
114
+
115
+ def sleep_wait
116
+ sleep 0.2
117
+ end
118
+ end
119
+
120
+ module M7
121
+ def busy_wait
122
+ t = Time.now.to_f
123
+ while Time.now.to_f - t < 0.3; end
124
+ end
125
+
126
+ def sleep_wait
127
+ sleep 0.3
128
+ end
129
+ end
130
+
131
+ class C8
132
+ include M7
133
+ extend M7
134
+ end
135
+
136
+ def self.ruby_major_version
137
+ match = RUBY_VERSION.match(/(\d)\.(\d)/)
138
+ return Integer(match[1])
139
+ end
140
+
141
+ def self.ruby_minor_version
142
+ match = RUBY_VERSION.match(/(\d)\.(\d)/)
143
+ return Integer(match[2])
144
+ end
145
+
146
+ def self.parent_object
147
+ if ruby_major_version == 1 && ruby_minor_version == 8
148
+ Object
149
+ else
150
+ BasicObject
151
+ end
152
+ end
153
+
154
+ def self.ruby_2?
155
+ ruby_major_version == 2
156
+ end
157
+
158
+ # store printer output in this directory
159
+ def self.tmpdir
160
+ File.expand_path('../../tmp', __FILE__)
161
+ end
162
+ end
163
+
164
+ module MemoryTestHelper
165
+ def memory_test_helper
166
+ result = RubyProf.profile {Array.new}
167
+ total = result.threads.first.methods.inject(0) { |sum, m| sum + m.total_time }
168
+ assert(total < 1_000_000, 'Total should not have subtract overflow error')
169
+ total
170
+ end
171
+ end
172
+
173
+ module PrinterTestHelper
174
+ Metrics = Struct.new(:name, :total, :self_t, :wait, :child, :calls)
175
+ class Metrics
176
+ def pp
177
+ "%s[total: %.2f, self: %.2f, wait: %.2f, child: %.2f, calls: %s]" %
178
+ [name, total, self_t, wait, child, calls]
179
+ end
180
+ end
181
+
182
+ Entry = Struct.new(:total_p, :self_p, :metrics, :parents, :children)
183
+ class Entry
184
+ def child(name)
185
+ children.detect{|m| m.name == name}
186
+ end
187
+
188
+ def parent(name)
189
+ parents.detect{|m| m.name == name}
190
+ end
191
+
192
+ def pp
193
+ res = ""
194
+ res << "NODE (total%%: %.2f, self%%: %.2f) %s\n" % [total_p, self_p, metrics.pp]
195
+ res << " PARENTS:\n"
196
+ parents.each {|m| res << " " + m.pp << "\n"}
197
+ res << " CHILDREN:\n"
198
+ children.each {|m| res << " " + m.pp << "\n"}
199
+ res
200
+ end
201
+ end
202
+
203
+ class MetricsArray < Array
204
+ def metrics_for(name)
205
+ detect {|e| e.metrics.name == name}
206
+ end
207
+
208
+ def pp(io = STDOUT)
209
+ entries = map do |e|
210
+ begin
211
+ e.pp
212
+ rescue
213
+ puts $!.message + e.inspect
214
+ ""
215
+ end
216
+ end
217
+ io.puts entries.join("--------------------------------------------------\n")
218
+ end
219
+
220
+ def self.parse(str)
221
+ res = new
222
+ entry = nil
223
+ relatives = []
224
+ state = :preamble
225
+
226
+ str.each_line do |l|
227
+ line = l.chomp.strip
228
+ if line =~ /-----/
229
+ if state == :preamble
230
+ state = :parsing_parents
231
+ entry = Entry.new
232
+ elsif state == :parsing_parents
233
+ entry = Entry.new
234
+ elsif state == :parsing_children
235
+ entry.children = relatives
236
+ res << entry
237
+ entry = Entry.new
238
+ relatives = []
239
+ state = :parsing_parents
240
+ end
241
+ elsif line =~ /^\s*$/ || line =~ /indicates recursively called methods/
242
+ next
243
+ elsif state != :preamble
244
+ elements = line.split(/\s+/)
245
+ method = elements.pop
246
+ numbers = elements[0..-2].map(&:to_f)
247
+ metrics = Metrics.new(method, *numbers[-4..-1], elements[-1])
248
+ if numbers.size == 6
249
+ entry.metrics = metrics
250
+ entry.total_p = numbers[0]
251
+ entry.self_p = numbers[1]
252
+ entry.parents = relatives
253
+ entry.children = relatives = []
254
+ state = :parsing_children
255
+ res << entry
256
+ else
257
+ relatives << metrics
258
+ end
259
+ end
260
+ end
261
+ res
262
+ end
263
+ end
264
+ end