ruby-prof 1.1.0-x64-mingw32 → 1.4.2-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +48 -1
  3. data/Rakefile +2 -14
  4. data/bin/ruby-prof +100 -152
  5. data/ext/ruby_prof/extconf.rb +8 -28
  6. data/ext/ruby_prof/rp_aggregate_call_tree.c +59 -0
  7. data/ext/ruby_prof/rp_aggregate_call_tree.h +13 -0
  8. data/ext/ruby_prof/rp_allocation.c +67 -59
  9. data/ext/ruby_prof/rp_allocation.h +3 -3
  10. data/ext/ruby_prof/rp_call_tree.c +369 -0
  11. data/ext/ruby_prof/rp_call_tree.h +43 -0
  12. data/ext/ruby_prof/rp_call_trees.c +288 -0
  13. data/ext/ruby_prof/rp_call_trees.h +28 -0
  14. data/ext/ruby_prof/rp_measure_allocations.c +12 -14
  15. data/ext/ruby_prof/rp_measure_process_time.c +12 -14
  16. data/ext/ruby_prof/rp_measure_wall_time.c +17 -15
  17. data/ext/ruby_prof/rp_measurement.c +47 -40
  18. data/ext/ruby_prof/rp_measurement.h +7 -7
  19. data/ext/ruby_prof/rp_method.c +116 -255
  20. data/ext/ruby_prof/rp_method.h +31 -39
  21. data/ext/ruby_prof/rp_profile.c +316 -303
  22. data/ext/ruby_prof/rp_profile.h +1 -3
  23. data/ext/ruby_prof/rp_stack.c +122 -106
  24. data/ext/ruby_prof/rp_stack.h +17 -20
  25. data/ext/ruby_prof/rp_thread.c +136 -111
  26. data/ext/ruby_prof/rp_thread.h +12 -9
  27. data/ext/ruby_prof/ruby_prof.c +27 -23
  28. data/ext/ruby_prof/ruby_prof.h +9 -0
  29. data/ext/ruby_prof/vc/ruby_prof.sln +8 -0
  30. data/ext/ruby_prof/vc/ruby_prof.vcxproj +22 -7
  31. data/lib/2.7/ruby_prof.so +0 -0
  32. data/lib/ruby-prof.rb +5 -5
  33. data/lib/ruby-prof/assets/call_stack_printer.html.erb +4 -7
  34. data/lib/ruby-prof/assets/graph_printer.html.erb +5 -6
  35. data/lib/ruby-prof/{call_info.rb → call_tree.rb} +6 -6
  36. data/lib/ruby-prof/call_tree_visitor.rb +36 -0
  37. data/lib/ruby-prof/compatibility.rb +0 -10
  38. data/lib/ruby-prof/measurement.rb +5 -2
  39. data/lib/ruby-prof/method_info.rb +3 -15
  40. data/lib/ruby-prof/printers/abstract_printer.rb +12 -2
  41. data/lib/ruby-prof/printers/call_info_printer.rb +12 -10
  42. data/lib/ruby-prof/printers/call_stack_printer.rb +20 -22
  43. data/lib/ruby-prof/printers/call_tree_printer.rb +1 -1
  44. data/lib/ruby-prof/printers/dot_printer.rb +3 -3
  45. data/lib/ruby-prof/printers/flat_printer.rb +3 -2
  46. data/lib/ruby-prof/printers/graph_printer.rb +4 -5
  47. data/lib/ruby-prof/printers/multi_printer.rb +2 -2
  48. data/lib/ruby-prof/profile.rb +8 -4
  49. data/lib/ruby-prof/rack.rb +51 -127
  50. data/lib/ruby-prof/thread.rb +3 -18
  51. data/lib/ruby-prof/version.rb +1 -1
  52. data/ruby-prof.gemspec +7 -0
  53. data/test/alias_test.rb +42 -45
  54. data/test/basic_test.rb +0 -86
  55. data/test/{call_info_visitor_test.rb → call_tree_visitor_test.rb} +6 -5
  56. data/test/call_trees_test.rb +66 -0
  57. data/test/exclude_methods_test.rb +17 -12
  58. data/test/fiber_test.rb +95 -39
  59. data/test/gc_test.rb +36 -42
  60. data/test/inverse_call_tree_test.rb +175 -0
  61. data/test/line_number_test.rb +67 -70
  62. data/test/marshal_test.rb +7 -13
  63. data/test/measure_allocations_test.rb +224 -234
  64. data/test/measure_allocations_trace_test.rb +224 -234
  65. data/test/measure_memory_trace_test.rb +814 -469
  66. data/test/measure_process_time_test.rb +0 -64
  67. data/test/measure_times.rb +2 -0
  68. data/test/measure_wall_time_test.rb +34 -58
  69. data/test/pause_resume_test.rb +19 -10
  70. data/test/prime.rb +1 -3
  71. data/test/prime_script.rb +6 -0
  72. data/test/printer_call_stack_test.rb +0 -1
  73. data/test/printer_call_tree_test.rb +0 -1
  74. data/test/printer_flat_test.rb +61 -30
  75. data/test/printer_graph_html_test.rb +0 -1
  76. data/test/printer_graph_test.rb +3 -4
  77. data/test/printers_test.rb +2 -2
  78. data/test/printing_recursive_graph_test.rb +1 -1
  79. data/test/profile_test.rb +16 -0
  80. data/test/rack_test.rb +0 -64
  81. data/test/recursive_test.rb +50 -54
  82. data/test/start_stop_test.rb +19 -19
  83. data/test/test_helper.rb +6 -17
  84. data/test/thread_test.rb +11 -11
  85. data/test/unique_call_path_test.rb +25 -95
  86. metadata +22 -11
  87. data/ext/ruby_prof/rp_call_info.c +0 -271
  88. data/ext/ruby_prof/rp_call_info.h +0 -35
  89. data/lib/2.6.5/ruby_prof.so +0 -0
  90. data/lib/ruby-prof/call_info_visitor.rb +0 -38
  91. data/test/parser_timings.rb +0 -24
@@ -8,66 +8,122 @@ require 'set'
8
8
 
9
9
  # -- Tests ----
10
10
  class FiberTest < TestCase
11
- def fiber_test
12
- @fiber_ids << Fiber.current.object_id
11
+
12
+ def enumerator_with_fibers
13
13
  enum = Enumerator.new do |yielder|
14
14
  [1,2].each do |x|
15
- @fiber_ids << Fiber.current.object_id
16
- sleep 0.1
17
15
  yielder.yield x
18
16
  end
19
17
  end
20
- while true
21
- begin
22
- enum.next
23
- rescue StopIteration
24
- break
25
- end
26
- end
27
- sleep 0.1
18
+
19
+ enum.next
20
+ enum.next
21
+ end
22
+
23
+ def fiber_yield_resume
24
+ fiber = Fiber.new do
25
+ Fiber.yield 1
26
+ Fiber.yield 2
27
+ end
28
+
29
+ fiber.resume
30
+ fiber.resume
28
31
  end
29
32
 
30
33
  def setup
31
34
  # Need to use wall time for this test due to the sleep calls
32
35
  RubyProf::measure_mode = RubyProf::WALL_TIME
33
- @fiber_ids = Set.new
34
- @root_fiber = Fiber.current.object_id
35
- @thread_id = Thread.current.object_id
36
36
  end
37
37
 
38
38
  def test_fibers
39
- result = RubyProf.profile { fiber_test }
40
- profiled_fiber_ids = result.threads.map(&:fiber_id)
41
- assert_equal(2, result.threads.length)
42
- assert_equal([@thread_id], result.threads.map(&:id).uniq)
43
- assert_equal(@fiber_ids, Set.new(profiled_fiber_ids))
39
+ result = RubyProf.profile { enumerator_with_fibers }
40
+
41
+ assert_equal(2, result.threads.size)
42
+
43
+ thread1 = result.threads[0]
44
+ methods = thread1.methods.sort.reverse
45
+ assert_equal(5, methods.count)
46
+
47
+ method = methods[0]
48
+ assert_equal('FiberTest#test_fibers', method.full_name)
49
+ assert_equal(1, method.called)
50
+
51
+ method = methods[1]
52
+ assert_equal('FiberTest#enumerator_with_fibers', method.full_name)
53
+ assert_equal(1, method.called)
44
54
 
45
- assert profiled_fiber_ids.include?(@root_fiber)
46
- assert(root_fiber_profile = result.threads.detect{|t| t.fiber_id == @root_fiber})
47
- assert(enum_fiber_profile = result.threads.detect{|t| t.fiber_id != @root_fiber})
55
+ method = methods[2]
56
+ assert_equal('Enumerator#next', method.full_name)
57
+ assert_equal(2, method.called)
48
58
 
49
- assert_in_delta(0.3, root_fiber_profile.total_time, 0.05)
50
- assert_in_delta(0.2, enum_fiber_profile.total_time, 0.05)
59
+ method = methods[3]
60
+ assert_equal('Class#new', method.full_name)
61
+ assert_equal(1, method.called)
51
62
 
52
- assert(method_next = root_fiber_profile.methods.detect{|m| m.full_name == "Enumerator#next"})
53
- assert(method_each = enum_fiber_profile.methods.detect{|m| m.full_name == "Enumerator#each"})
63
+ method = methods[4]
64
+ assert_equal('Enumerator#initialize', method.full_name)
65
+ assert_equal(1, method.called)
54
66
 
55
- assert_in_delta(0.2, method_next.total_time, 0.05)
56
- assert_in_delta(0.2, method_each.total_time, 0.05)
67
+ thread2 = result.threads[1]
68
+ methods = thread2.methods.sort.reverse
69
+ assert_equal(4, methods.count)
70
+
71
+ method = methods[0]
72
+ assert_equal('Enumerator#each', method.full_name)
73
+ assert_equal(1, method.called)
74
+
75
+ method = methods[1]
76
+ assert_equal('Enumerator::Generator#each', method.full_name)
77
+ assert_equal(1, method.called)
78
+
79
+ method = methods[2]
80
+ assert_equal('Array#each', method.full_name)
81
+ assert_equal(1, method.called)
82
+
83
+ method = methods[3]
84
+ assert_equal('Enumerator::Yielder#yield', method.full_name)
85
+ assert_equal(2, method.called)
57
86
  end
58
87
 
59
- def test_merged_fibers
60
- result = RubyProf.profile(merge_fibers: true) { fiber_test }
61
- assert_equal(1, result.threads.length)
88
+ def test_fiber_resume
89
+ result = RubyProf.profile { fiber_yield_resume }
90
+
91
+ assert_equal(2, result.threads.size)
92
+
93
+ thread1 = result.threads[0]
94
+ methods = thread1.methods.sort.reverse
95
+ assert_equal(5, methods.count)
96
+
97
+ method = methods[0]
98
+ assert_equal('FiberTest#test_fiber_resume', method.full_name)
99
+ assert_equal(1, method.called)
100
+
101
+ method = methods[1]
102
+ assert_equal('FiberTest#fiber_yield_resume', method.full_name)
103
+ assert_equal(1, method.called)
104
+
105
+ method = methods[2]
106
+ assert_equal('Fiber#resume', method.full_name)
107
+ assert_equal(2, method.called)
108
+
109
+ method = methods[3]
110
+ assert_equal('Class#new', method.full_name)
111
+ assert_equal(1, method.called)
112
+
113
+ method = methods[4]
114
+ assert_equal('Fiber#initialize', method.full_name)
115
+ assert_equal(1, method.called)
62
116
 
63
- thread = result.threads.first
64
- assert_equal(thread.id, thread.fiber_id)
65
- assert_in_delta(0.3, thread.total_time, 0.05)
117
+ thread1 = result.threads[1]
118
+ methods = thread1.methods.sort.reverse
119
+ assert_equal(2, methods.count)
66
120
 
67
- assert(method_next = thread.methods.detect{|m| m.full_name == "Enumerator#next"})
68
- assert(method_each = thread.methods.detect{|m| m.full_name == "Enumerator#each"})
121
+ method = methods[0]
122
+ assert_equal('FiberTest#fiber_yield_resume', method.full_name)
123
+ assert_equal(1, method.called)
69
124
 
70
- assert_in_delta(0.2, method_next.total_time, 0.05)
71
- assert_in_delta(0.2, method_each.total_time, 0.05)
125
+ method = methods[1]
126
+ assert_equal('<Class::Fiber>#yield', method.full_name)
127
+ assert_equal(2, method.called)
72
128
  end
73
129
  end
@@ -2,8 +2,17 @@
2
2
  # encoding: UTF-8
3
3
 
4
4
  require File.expand_path('../test_helper', __FILE__)
5
+ Minitest::Test.i_suck_and_my_tests_are_order_dependent!
5
6
 
6
7
  class GcTest < TestCase
8
+ def setup
9
+ GC.stress = true
10
+ end
11
+
12
+ def teardown
13
+ GC.stress = false
14
+ end
15
+
7
16
  def some_method
8
17
  Array.new(3 * 4)
9
18
  end
@@ -15,82 +24,67 @@ class GcTest < TestCase
15
24
  end
16
25
 
17
26
  def test_hold_onto_thread
18
- threads = 1000.times.reduce(Array.new) do |array, i|
27
+ threads = 5.times.reduce(Array.new) do |array, i|
19
28
  array.concat(run_profile.threads)
20
- GC.start
21
29
  array
22
30
  end
23
31
 
24
32
  threads.each do |thread|
25
- error = assert_raises(RuntimeError) do
26
- thread.id
27
- end
28
- assert_match(/has already been freed/, error.message)
33
+ refute_nil(thread.id)
29
34
  end
30
- assert(true)
31
35
  end
32
36
 
37
+
33
38
  def test_hold_onto_method
34
- methods = 1000.times.reduce(Array.new) do |array, i|
35
- array.concat(run_profile.threads.map(&:methods).flatten)
36
- GC.start
39
+ methods = 5.times.reduce(Array.new) do |array, i|
40
+ profile = run_profile
41
+ array.concat(profile.threads.map(&:methods).flatten)
37
42
  array
38
43
  end
39
44
 
40
45
  methods.each do |method|
41
- error = assert_raises(RuntimeError) do
42
- method.method_name
43
- end
44
- assert_match(/has already been freed/, error.message)
46
+ refute_nil(method.method_name)
45
47
  end
46
- assert(true)
47
48
  end
48
49
 
49
- def test_hold_onto_parent_callers
50
- call_infos = 1000.times.reduce(Array.new) do |array, i|
51
- array.concat(run_profile.threads.map(&:methods).flatten.map(&:callers).flatten)
52
- GC.start
50
+ def test_hold_onto_call_trees
51
+ method_call_infos = 5.times.reduce(Array.new) do |array, i|
52
+ profile = run_profile
53
+ call_trees = profile.threads.map(&:methods).flatten.map(&:call_trees).flatten
54
+ array.concat(call_trees)
53
55
  array
54
56
  end
55
57
 
56
- call_infos.each do |call_info|
57
- error = assert_raises(RuntimeError) do
58
- call_info.source_file
59
- end
60
- assert_match(/has already been freed/, error.message)
58
+ method_call_infos.each do |call_trees|
59
+ refute_empty(call_trees.call_trees)
61
60
  end
62
- assert(true)
63
61
  end
64
62
 
65
- def test_hold_onto_parent_callees
66
- call_infos = 1000.times.reduce(Array.new) do |array, i|
67
- array.concat(run_profile.threads.map(&:methods).flatten.map(&:callees).flatten)
68
- GC.start
63
+ def test_hold_onto_measurements
64
+ measurements = 5.times.reduce(Array.new) do |array, i|
65
+ profile = run_profile
66
+ measurements = profile.threads.map(&:methods).flatten.map(&:measurement)
67
+ array.concat(measurements)
69
68
  array
70
69
  end
71
70
 
72
- call_infos.each do |call_info|
71
+ measurements.each do |measurement|
73
72
  error = assert_raises(RuntimeError) do
74
- call_info.source_file
73
+ measurement.total_time
75
74
  end
76
- assert_match(/has already been freed/, error.message)
75
+ assert_match(/RubyProf::Measurement instance has already been freed/, error.message)
77
76
  end
78
77
  assert(true)
79
78
  end
80
79
 
81
- def test_hold_onto_measurements
82
- measurements = 1000.times.reduce(Array.new) do |array, i|
83
- array.concat(run_profile.threads.map(&:methods).flatten.map(&:callers).flatten.map(&:measurement))
84
- GC.start
80
+ def test_hold_onto_root_call_tree
81
+ call_trees = 5.times.reduce(Array.new) do |array, i|
82
+ array.concat(run_profile.threads.map(&:call_tree))
85
83
  array
86
84
  end
87
85
 
88
- measurements.each do |measurement|
89
- error = assert_raises(RuntimeError) do
90
- measurement.total_time
91
- end
92
- assert_match(/has already been freed/, error.message)
86
+ call_trees.each do |call_tree|
87
+ refute_nil(call_tree.source_file)
93
88
  end
94
- assert(true)
95
89
  end
96
90
  end
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ class InverseCallTreeTest < TestCase
7
+ INVERSE_DEPTH = 5
8
+
9
+ def setup
10
+ # Need to use wall time for this test due to the sleep calls
11
+ RubyProf::measure_mode = RubyProf::WALL_TIME
12
+ end
13
+
14
+ INVERSE_DEPTH.times do |i|
15
+ if i == 0
16
+ define_method("method_#{i}") do
17
+ sleep_amount = (i + 1) * 0.05
18
+ RubyProf.start
19
+ sleep(sleep_amount)
20
+ end
21
+ else
22
+ define_method("method_#{i}") do
23
+ method_name = "method_#{i-1}"
24
+ sleep_amount = (i + 1) * 0.05
25
+ self.send(method_name.to_sym)
26
+ sleep(sleep_amount)
27
+ end
28
+ end
29
+ end
30
+
31
+ def test_inverse
32
+ method_name = "method_#{INVERSE_DEPTH - 1}"
33
+ self.send(method_name.to_sym)
34
+ profile = RubyProf.stop
35
+
36
+ assert_equal(1, profile.threads.count)
37
+
38
+ thread = profile.threads.first
39
+ assert_in_delta(0.79, thread.total_time, 0.05)
40
+
41
+ assert_equal(7, thread.methods.length)
42
+ methods = thread.methods.sort.reverse
43
+
44
+ # InverseCallTreeTest#test_inverse
45
+ method = methods[0]
46
+ assert_equal('InverseCallTreeTest#test_inverse', method.full_name)
47
+ assert_equal(34, method.line)
48
+
49
+ assert_equal(0, method.call_trees.callers.count)
50
+
51
+ assert_equal(1, method.call_trees.callees.count)
52
+ call_tree = method.call_trees.callees[0]
53
+ assert_equal('InverseCallTreeTest#method_4', call_tree.target.full_name)
54
+ assert_equal(26, call_tree.line)
55
+
56
+ # InverseCallTreeTest#method_4
57
+ method = methods[1]
58
+ assert_equal('InverseCallTreeTest#method_4', method.full_name)
59
+ assert_equal(26, method.line)
60
+
61
+ assert_equal(1, method.call_trees.callers.count)
62
+ call_tree = method.call_trees.callers[0]
63
+ assert_equal('InverseCallTreeTest#test_inverse', call_tree.parent.target.full_name)
64
+ assert_equal(26, call_tree.line)
65
+
66
+ assert_equal(2, method.call_trees.callees.count)
67
+ call_tree = method.call_trees.callees[0]
68
+ assert_equal('InverseCallTreeTest#method_3', call_tree.target.full_name)
69
+ assert_equal(26, call_tree.line)
70
+
71
+ # Kernel#sleep
72
+ method = methods[2]
73
+ assert_equal('Kernel#sleep', method.full_name)
74
+ assert_equal(0, method.line)
75
+
76
+ assert_equal(5, method.call_trees.callers.count)
77
+ call_tree = method.call_trees.callers[0]
78
+ assert_equal('InverseCallTreeTest#method_0', call_tree.parent.target.full_name)
79
+ assert_equal(19, call_tree.line)
80
+
81
+ call_tree = method.call_trees.callers[1]
82
+ assert_equal('InverseCallTreeTest#method_1', call_tree.parent.target.full_name)
83
+ assert_equal(26, call_tree.line)
84
+
85
+ call_tree = method.call_trees.callers[2]
86
+ assert_equal('InverseCallTreeTest#method_2', call_tree.parent.target.full_name)
87
+ assert_equal(26, call_tree.line)
88
+ call_tree = method.call_trees.callers[3]
89
+
90
+ assert_equal('InverseCallTreeTest#method_3', call_tree.parent.target.full_name)
91
+ assert_equal(26, call_tree.line)
92
+
93
+ call_tree = method.call_trees.callers[4]
94
+ assert_equal('InverseCallTreeTest#method_4', call_tree.parent.target.full_name)
95
+ assert_equal(26, call_tree.line)
96
+
97
+ assert_equal(0, method.call_trees.callees.count)
98
+
99
+ # InverseCallTreeTest#method_3
100
+ method = methods[3]
101
+ assert_equal('InverseCallTreeTest#method_3', method.full_name)
102
+ assert_equal(26, method.line)
103
+
104
+ assert_equal(1, method.call_trees.callers.count)
105
+ call_tree = method.call_trees.callers[0]
106
+ assert_equal('InverseCallTreeTest#method_4', call_tree.parent.target.full_name)
107
+ assert_equal(26, call_tree.line)
108
+
109
+ assert_equal(2, method.call_trees.callees.count)
110
+ call_tree = method.call_trees.callees[0]
111
+ assert_equal('InverseCallTreeTest#method_2', call_tree.target.full_name)
112
+ assert_equal(26, call_tree.line)
113
+
114
+ call_tree = method.call_trees.callees[1]
115
+ assert_equal('Kernel#sleep', call_tree.target.full_name)
116
+ assert_equal(26, call_tree.line)
117
+
118
+ # InverseCallTreeTest#method_2
119
+ method = methods[4]
120
+ assert_equal('InverseCallTreeTest#method_2', method.full_name)
121
+ assert_equal(26, method.line)
122
+
123
+ assert_equal(1, method.call_trees.callers.count)
124
+ call_tree = method.call_trees.callers[0]
125
+ assert_equal('InverseCallTreeTest#method_3', call_tree.parent.target.full_name)
126
+ assert_equal(26, call_tree.line)
127
+
128
+ assert_equal(2, method.call_trees.callees.count)
129
+ call_tree = method.call_trees.callees[0]
130
+ assert_equal('InverseCallTreeTest#method_1', call_tree.target.full_name)
131
+ assert_equal(26, call_tree.line)
132
+
133
+ call_tree = method.call_trees.callees[1]
134
+ assert_equal('Kernel#sleep', call_tree.target.full_name)
135
+ assert_equal(26, call_tree.line)
136
+
137
+ call_tree = method.call_trees.callees[1]
138
+ assert_equal('Kernel#sleep', call_tree.target.full_name)
139
+ assert_equal(26, call_tree.line)
140
+
141
+ # InverseCallTreeTest#method_1
142
+ method = methods[5]
143
+ assert_equal('InverseCallTreeTest#method_1', method.full_name)
144
+ assert_equal(26, method.line)
145
+
146
+ assert_equal(1, method.call_trees.callers.count)
147
+ call_tree = method.call_trees.callers[0]
148
+ assert_equal('InverseCallTreeTest#method_2', call_tree.parent.target.full_name)
149
+ assert_equal(26, call_tree.line)
150
+
151
+ assert_equal(2, method.call_trees.callees.count)
152
+ call_tree = method.call_trees.callees[0]
153
+ assert_equal('InverseCallTreeTest#method_0', call_tree.target.full_name)
154
+ assert_equal(19, call_tree.line)
155
+
156
+ call_tree = method.call_trees.callees[1]
157
+ assert_equal('Kernel#sleep', call_tree.target.full_name)
158
+ assert_equal(26, call_tree.line)
159
+
160
+ # InverseCallTreeTest#method_0
161
+ method = methods[6]
162
+ assert_equal('InverseCallTreeTest#method_0', method.full_name)
163
+ assert_equal(19, method.line)
164
+
165
+ assert_equal(1, method.call_trees.callers.count)
166
+ call_tree = method.call_trees.callers[0]
167
+ assert_equal('InverseCallTreeTest#method_1', call_tree.parent.target.full_name)
168
+ assert_equal(19, call_tree.line)
169
+
170
+ assert_equal(1, method.call_trees.callees.count)
171
+ call_tree = method.call_trees.callees[0]
172
+ assert_equal('Kernel#sleep', call_tree.target.full_name)
173
+ assert_equal(19, call_tree.line)
174
+ end
175
+ end