newrelic_rpm 3.6.7.159 → 3.6.8.164

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/CHANGELOG +14 -0
  2. data/lib/new_relic/agent/agent.rb +38 -35
  3. data/lib/new_relic/agent/agent_logger.rb +6 -47
  4. data/lib/new_relic/agent/beacon_configuration.rb +10 -4
  5. data/lib/new_relic/agent/browser_monitoring.rb +39 -33
  6. data/lib/new_relic/agent/commands/agent_command.rb +4 -4
  7. data/lib/new_relic/agent/commands/agent_command_router.rb +72 -10
  8. data/lib/new_relic/agent/commands/thread_profiler_session.rb +110 -0
  9. data/lib/new_relic/agent/commands/xray_session.rb +55 -0
  10. data/lib/new_relic/agent/commands/xray_session_collection.rb +158 -0
  11. data/lib/new_relic/agent/configuration/default_source.rb +61 -24
  12. data/lib/new_relic/agent/configuration/mask_defaults.rb +2 -2
  13. data/lib/new_relic/agent/configuration/server_source.rb +1 -1
  14. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +2 -0
  15. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +4 -10
  16. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +10 -11
  17. data/lib/new_relic/agent/memory_logger.rb +52 -0
  18. data/lib/new_relic/agent/new_relic_service.rb +4 -0
  19. data/lib/new_relic/agent/request_sampler.rb +32 -13
  20. data/lib/new_relic/agent/samplers/cpu_sampler.rb +6 -3
  21. data/lib/new_relic/agent/threading/agent_thread.rb +2 -1
  22. data/lib/new_relic/agent/threading/backtrace_node.rb +80 -27
  23. data/lib/new_relic/agent/threading/backtrace_service.rb +264 -0
  24. data/lib/new_relic/agent/threading/thread_profile.rb +79 -118
  25. data/lib/new_relic/agent/transaction/developer_mode_sample_buffer.rb +56 -0
  26. data/lib/new_relic/agent/transaction/force_persist_sample_buffer.rb +25 -0
  27. data/lib/new_relic/agent/transaction/slowest_sample_buffer.rb +25 -0
  28. data/lib/new_relic/agent/transaction/transaction_sample_buffer.rb +86 -0
  29. data/lib/new_relic/agent/transaction/xray_sample_buffer.rb +64 -0
  30. data/lib/new_relic/agent/transaction.rb +25 -4
  31. data/lib/new_relic/agent/transaction_sample_builder.rb +6 -10
  32. data/lib/new_relic/agent/transaction_sampler.rb +47 -202
  33. data/lib/new_relic/agent/worker_loop.rb +47 -39
  34. data/lib/new_relic/agent.rb +1 -1
  35. data/lib/new_relic/build.rb +2 -2
  36. data/lib/new_relic/coerce.rb +8 -0
  37. data/lib/new_relic/control/instance_methods.rb +1 -0
  38. data/lib/new_relic/rack/browser_monitoring.rb +15 -1
  39. data/lib/new_relic/rack/developer_mode.rb +1 -1
  40. data/lib/new_relic/transaction_sample.rb +20 -5
  41. data/lib/new_relic/version.rb +1 -1
  42. data/newrelic.yml +4 -6
  43. data/newrelic_rpm.gemspec +1 -1
  44. data/test/agent_helper.rb +11 -0
  45. data/test/environments/lib/environments/runner.rb +5 -1
  46. data/test/environments/rails21/Gemfile +2 -2
  47. data/test/environments/rails22/Gemfile +2 -2
  48. data/test/environments/rails23/Gemfile +2 -2
  49. data/test/environments/rails31/Gemfile +2 -2
  50. data/test/environments/rails32/Gemfile +2 -2
  51. data/test/multiverse/suites/agent_only/marshaling_test.rb +1 -1
  52. data/test/multiverse/suites/agent_only/testing_app.rb +6 -0
  53. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +5 -5
  54. data/test/multiverse/suites/agent_only/xray_sessions_test.rb +163 -0
  55. data/test/multiverse/suites/rails/request_statistics_test.rb +2 -2
  56. data/test/multiverse/suites/rails/view_instrumentation_test.rb +20 -21
  57. data/test/new_relic/agent/agent/connect_test.rb +0 -10
  58. data/test/new_relic/agent/agent_test.rb +27 -44
  59. data/test/new_relic/agent/browser_monitoring_test.rb +0 -52
  60. data/test/new_relic/agent/commands/agent_command_router_test.rb +150 -12
  61. data/test/new_relic/agent/commands/{thread_profiler_test.rb → thread_profiler_session_test.rb} +58 -19
  62. data/test/new_relic/agent/commands/xray_session_collection_test.rb +332 -0
  63. data/test/new_relic/agent/commands/xray_session_test.rb +42 -0
  64. data/test/new_relic/agent/configuration/manager_test.rb +2 -1
  65. data/test/new_relic/agent/configuration/server_source_test.rb +10 -10
  66. data/test/new_relic/agent/cpu_sampler_test.rb +50 -0
  67. data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +31 -0
  68. data/test/new_relic/agent/instrumentation/queue_time_test.rb +0 -1
  69. data/test/new_relic/agent/instrumentation/sequel_test.rb +1 -1
  70. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +0 -1
  71. data/test/new_relic/agent/memory_logger_test.rb +53 -0
  72. data/test/new_relic/agent/new_relic_service_test.rb +1 -1
  73. data/test/new_relic/agent/pipe_channel_manager_test.rb +4 -5
  74. data/test/new_relic/agent/request_sampler_test.rb +70 -20
  75. data/test/new_relic/agent/rules_engine_test.rb +6 -0
  76. data/test/new_relic/agent/threading/agent_thread_test.rb +2 -2
  77. data/test/new_relic/agent/threading/backtrace_node_test.rb +110 -17
  78. data/test/new_relic/agent/threading/backtrace_service_test.rb +567 -0
  79. data/test/new_relic/agent/threading/fake_thread.rb +4 -0
  80. data/test/new_relic/agent/threading/thread_profile_test.rb +141 -217
  81. data/test/new_relic/agent/threading/threaded_test_case.rb +3 -8
  82. data/test/new_relic/agent/transaction/developer_mode_sample_buffer_test.rb +69 -0
  83. data/test/new_relic/agent/transaction/force_persist_sample_buffer_test.rb +52 -0
  84. data/test/new_relic/agent/transaction/slowest_sample_buffer_test.rb +67 -0
  85. data/test/new_relic/agent/transaction/xray_sample_buffer_test.rb +71 -0
  86. data/test/new_relic/agent/transaction_sampler_test.rb +171 -307
  87. data/test/new_relic/agent/transaction_test.rb +33 -5
  88. data/test/new_relic/agent/worker_loop_test.rb +33 -11
  89. data/test/new_relic/coerce_test.rb +13 -0
  90. data/test/new_relic/fake_collector.rb +26 -3
  91. data/test/new_relic/multiverse_helpers.rb +2 -0
  92. data/test/new_relic/rack/browser_monitoring_test.rb +12 -0
  93. data/test/new_relic/rack/developer_mode_test.rb +2 -2
  94. data/test/new_relic/transaction_sample_test.rb +19 -2
  95. data/test/performance/lib/performance/console_reporter.rb +1 -1
  96. data/test/performance/lib/performance/test_case.rb +7 -3
  97. data/test/performance/script/runner +3 -0
  98. data/test/performance/suites/thread_profiling.rb +83 -0
  99. data/test/test_helper.rb +2 -2
  100. data.tar.gz.sig +0 -0
  101. metadata +32 -32
  102. metadata.gz.sig +1 -1
  103. data/lib/new_relic/agent/commands/thread_profiler.rb +0 -80
@@ -25,6 +25,12 @@ class RulesEngineTest < Test::Unit::TestCase
25
25
  assert_equal(['foo/*/bar/22', true], rule.apply('foo/1/bar/22'))
26
26
  end
27
27
 
28
+ def test_rules_can_apply_to_frozen_strings
29
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
30
+ 'replacement' => '*')
31
+ assert_equal(['foo/*/bar/22', true], rule.apply('foo/1/bar/22'.freeze))
32
+ end
33
+
28
34
  def test_rule_applies_grouping_with_replacements
29
35
  rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '([0-9]+)',
30
36
  'replacement' => '\\1\\1')
@@ -80,12 +80,12 @@ module NewRelic::Agent::Threading
80
80
  ]
81
81
 
82
82
  def test_scrubs_backtrace_when_not_profiling_agent_code
83
- result = AgentThread.scrub_backtrace(stub(:backtrace => TRACE), false)
83
+ result = AgentThread.scrub_backtrace(stub(:backtrace => TRACE.dup), false)
84
84
  assert_equal [TRACE[0], TRACE[2]], result
85
85
  end
86
86
 
87
87
  def test_doesnt_scrub_backtrace_when_profiling_agent_code
88
- result = AgentThread.scrub_backtrace(stub(:backtrace => TRACE), true)
88
+ result = AgentThread.scrub_backtrace(stub(:backtrace => TRACE.dup), true)
89
89
  assert_equal TRACE, result
90
90
  end
91
91
 
@@ -9,6 +9,33 @@ module NewRelic::Agent::Threading
9
9
  class BacktraceNodeTest < Test::Unit::TestCase
10
10
  SINGLE_LINE = "irb.rb:69:in `catch'"
11
11
 
12
+ def setup
13
+ @node = BacktraceNode.new(nil)
14
+ @single_trace = [
15
+ "irb.rb:69:in `catch'",
16
+ "irb.rb:69:in `start'",
17
+ "irb:12:in `<main>'"
18
+ ]
19
+ end
20
+
21
+ def assert_backtrace_trees_equal(a, b, original_a=a, original_b=b)
22
+ message = "Thread profiles did not match.\n\n"
23
+ message << "Expected tree:\n#{original_a.dump_string}\n\n"
24
+ message << "Actual tree:\n#{original_b.dump_string}\n"
25
+ assert_equal(a, b, message)
26
+ assert_equal(a.children, b.children, message)
27
+ a.children.zip(b.children) do |a_child, b_child|
28
+ assert_backtrace_trees_equal(a_child, b_child, a, b)
29
+ end
30
+ end
31
+
32
+ def create_node(frame, parent=nil, runnable_count=0)
33
+ node = BacktraceNode.new(frame)
34
+ parent.add_child_unless_present(node) if parent
35
+ node.runnable_count = runnable_count
36
+ node
37
+ end
38
+
12
39
  def test_single_node_converts_to_array
13
40
  line = "irb.rb:69:in `catch'"
14
41
  node = BacktraceNode.new(line)
@@ -23,8 +50,8 @@ module NewRelic::Agent::Threading
23
50
  def test_multiple_nodes_converts_to_array
24
51
  line = "irb.rb:69:in `catch'"
25
52
  child_line = "bacon.rb:42:in `yum'"
26
- node = BacktraceNode.new(line)
27
- child = BacktraceNode.new(child_line, node)
53
+ node = create_node(line)
54
+ create_node(child_line, node)
28
55
 
29
56
  assert_equal([
30
57
  ["irb.rb", "catch", 69],
@@ -41,7 +68,7 @@ module NewRelic::Agent::Threading
41
68
 
42
69
  def test_gracefully_handle_bad_values_in_to_array
43
70
  node = BacktraceNode.new(SINGLE_LINE)
44
- node.instance_variable_set(:@line_no, "blarg")
71
+ node.stubs(:parse_backtrace_frame).returns(["irb.rb", "catch", "blarg"])
45
72
  node.runnable_count = Rational(10, 1)
46
73
 
47
74
  assert_equal([
@@ -55,42 +82,108 @@ module NewRelic::Agent::Threading
55
82
  parent = BacktraceNode.new(SINGLE_LINE)
56
83
  child = BacktraceNode.new(SINGLE_LINE)
57
84
 
58
- parent.add_child(child)
59
- parent.add_child(child)
85
+ parent.add_child_unless_present(child)
86
+ parent.add_child_unless_present(child)
60
87
 
61
88
  assert_equal 1, parent.children.size
62
89
  end
63
90
 
64
91
  def test_prune_keeps_children
65
- parent = BacktraceNode.new(SINGLE_LINE)
66
- child = BacktraceNode.new(SINGLE_LINE, parent)
92
+ parent = create_node(SINGLE_LINE)
93
+ child = create_node(SINGLE_LINE, parent)
67
94
 
68
- parent.prune!
95
+ parent.prune!([])
69
96
 
70
97
  assert_equal [child], parent.children
71
98
  end
72
99
 
73
100
  def test_prune_removes_children
74
- parent = BacktraceNode.new(SINGLE_LINE)
75
- child = BacktraceNode.new(SINGLE_LINE, parent)
101
+ parent = create_node(SINGLE_LINE)
102
+ child = create_node(SINGLE_LINE, parent)
76
103
 
77
- child.to_prune = true
78
- parent.prune!
104
+ parent.prune!([child])
79
105
 
80
106
  assert_equal [], parent.children
81
107
  end
82
108
 
83
109
  def test_prune_removes_grandchildren
84
- parent = BacktraceNode.new(SINGLE_LINE)
85
- child = BacktraceNode.new(SINGLE_LINE, parent)
86
- grandchild = BacktraceNode.new(SINGLE_LINE, child)
110
+ parent = create_node(SINGLE_LINE)
111
+ child = create_node(SINGLE_LINE, parent)
112
+ grandchild = create_node(SINGLE_LINE, child)
87
113
 
88
- grandchild.to_prune = true
89
- parent.prune!
114
+ parent.prune!([grandchild])
90
115
 
91
116
  assert_equal [child], parent.children
92
117
  assert_equal [], child.children
93
118
  end
94
119
 
120
+ def test_aggregate_empty_trace
121
+ @node.aggregate([])
122
+ assert @node.empty?
123
+ end
124
+
125
+ def test_aggregate_builds_tree_from_first_trace
126
+ @node.aggregate(@single_trace)
127
+
128
+ root = BacktraceNode.new(nil)
129
+ tree = create_node(@single_trace[-1], root, 1)
130
+ child = create_node(@single_trace[-2], tree, 1)
131
+ create_node(@single_trace[-3], child, 1)
132
+
133
+ assert_backtrace_trees_equal root, @node
134
+ end
135
+
136
+ def test_aggregate_builds_tree_from_overlapping_traces
137
+ @node.aggregate(@single_trace)
138
+ @node.aggregate(@single_trace)
139
+
140
+ root = BacktraceNode.new(nil)
141
+ tree = create_node(@single_trace[-1], root, 2)
142
+ child = create_node(@single_trace[-2], tree, 2)
143
+ create_node(@single_trace[-3], child, 2)
144
+
145
+ assert_backtrace_trees_equal root, @node
146
+ end
147
+
148
+ def test_aggregate_builds_tree_from_diverging_traces
149
+ backtrace1 = [
150
+ "baz.rb:3:in `baz'",
151
+ "bar.rb:2:in `bar'",
152
+ "foo.rb:1:in `foo'"
153
+ ]
154
+
155
+ backtrace2 = [
156
+ "wiggle.rb:3:in `wiggle'",
157
+ "qux.rb:2:in `qux'",
158
+ "foo.rb:1:in `foo'"
159
+ ]
160
+
161
+ @node.aggregate(backtrace1)
162
+ @node.aggregate(backtrace2)
163
+
164
+ root = BacktraceNode.new(nil)
165
+
166
+ tree = create_node(backtrace1.last, root, 2)
167
+
168
+ bar_node = create_node(backtrace1[1], tree, 1)
169
+ create_node(backtrace1[0], bar_node, 1)
170
+
171
+ qux_node = create_node(backtrace2[1], tree, 1)
172
+ create_node(backtrace2[0], qux_node, 1)
173
+
174
+ assert_backtrace_trees_equal(root, @node)
175
+ end
176
+
177
+ def test_aggregate_doesnt_create_duplicate_children
178
+ @node.aggregate(@single_trace)
179
+ @node.aggregate(@single_trace)
180
+
181
+ root = BacktraceNode.new(nil)
182
+ tree = create_node(@single_trace[-1], root, 2)
183
+ child = create_node(@single_trace[-2], tree, 2)
184
+ create_node(@single_trace[-3], child, 2)
185
+
186
+ assert_backtrace_trees_equal(root, @node)
187
+ end
95
188
  end
96
189
  end
@@ -0,0 +1,567 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
6
+
7
+ require 'new_relic/agent/threading/backtrace_service'
8
+ require 'new_relic/agent/threading/threaded_test_case'
9
+
10
+ if NewRelic::Agent::Threading::BacktraceService.is_supported?
11
+
12
+ module NewRelic::Agent::Threading
13
+ class BacktraceServiceTest < Test::Unit::TestCase
14
+ include ThreadedTestCase
15
+
16
+ def setup
17
+ freeze_time
18
+ NewRelic::Agent.instance.stats_engine.clear_stats
19
+ @event_listener = NewRelic::Agent::EventListener.new
20
+ @service = BacktraceService.new(@event_listener)
21
+ setup_fake_threads
22
+ end
23
+
24
+ def teardown
25
+ NewRelic::Agent.instance.stats_engine.clear_stats
26
+ @service.stop
27
+ teardown_fake_threads
28
+ end
29
+
30
+ def test_starts_when_the_first_subscription_is_added
31
+ fake_worker_loop(@service)
32
+
33
+ @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
34
+ assert @service.running?
35
+ end
36
+
37
+ def test_stops_when_subscription_is_removed
38
+ fake_worker_loop(@service)
39
+
40
+ @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
41
+ assert @service.running?
42
+
43
+ @service.unsubscribe(BacktraceService::ALL_TRANSACTIONS)
44
+ assert !@service.running?
45
+ end
46
+
47
+ def test_stops_only_once_all_subscriptions_are_removed
48
+ fake_worker_loop(@service)
49
+
50
+ @service.subscribe('foo')
51
+ @service.subscribe('bar')
52
+ assert @service.running?
53
+
54
+ @service.unsubscribe('bar')
55
+ assert @service.running?
56
+
57
+ @service.unsubscribe('foo')
58
+ assert !@service.running?
59
+ end
60
+
61
+ def test_stop_clears_buffered_backtraces
62
+ fake_worker_loop(@service)
63
+
64
+ thread = fake_thread(:bucket => :request)
65
+
66
+ @service.subscribe('foo')
67
+ @service.poll
68
+ @service.unsubscribe('foo')
69
+
70
+ assert_equal 0, @service.buffer.size
71
+ end
72
+
73
+ def test_harvest_returns_thread_profiles
74
+ fake_worker_loop(@service)
75
+
76
+ profile = @service.subscribe('foo')
77
+ harvested_profile = @service.harvest('foo')
78
+ assert_same(profile, harvested_profile)
79
+ end
80
+
81
+ def test_harvest_resets_thread_profiles
82
+ fake_worker_loop(@service)
83
+
84
+ @service.subscribe('foo')
85
+ profile1 = @service.harvest('foo')
86
+ profile2 = @service.harvest('foo')
87
+
88
+ assert(profile1)
89
+ assert(profile2)
90
+ assert_not_same(profile1, profile2)
91
+ end
92
+
93
+ def test_harvest_passes_on_original_args_to_new_thread_profiles
94
+ fake_worker_loop(@service)
95
+
96
+ args = { 'profile_id' => 42, 'duration' => 99, 'sample_period' => 0.1 }
97
+ @service.subscribe('foo', args)
98
+ @service.harvest('foo')
99
+ profile = @service.harvest('foo')
100
+
101
+ assert_equal(args, profile.command_arguments)
102
+ end
103
+
104
+ def test_harvest_sets_finished_at_on_returned_thread_profile
105
+ fake_worker_loop(@service)
106
+
107
+ t0 = Time.now
108
+ @service.subscribe('foo')
109
+ harvested_profile = @service.harvest('foo')
110
+
111
+ assert_equal t0, harvested_profile.finished_at
112
+ end
113
+
114
+ def test_harvest_returns_nil_if_never_subscribed
115
+ fake_worker_loop(@service)
116
+
117
+ profile = @service.harvest('diggle')
118
+ assert_nil(profile)
119
+ end
120
+
121
+ def test_poll_forwards_backtraces_to_subscribed_profiles
122
+ fake_worker_loop(@service)
123
+
124
+ bt0, bt1 = mock('bt0'), mock('bt1')
125
+
126
+ thread0 = fake_thread(:bucket => :request, :backtrace => bt0)
127
+ thread1 = fake_thread(:bucket => :differenter_request, :backtrace => bt1)
128
+
129
+ profile = @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
130
+ profile.expects(:aggregate).with(bt0, :request, thread0)
131
+ profile.expects(:aggregate).with(bt1, :differenter_request, thread1)
132
+
133
+ @service.poll
134
+ end
135
+
136
+ def test_poll_does_not_forward_ignored_backtraces_to_profiles
137
+ fake_worker_loop(@service)
138
+
139
+ fake_thread(:bucket => :ignore)
140
+
141
+ profile = @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
142
+ profile.expects(:aggregate).never
143
+
144
+ @service.poll
145
+ end
146
+
147
+ def test_poll_scrubs_backtraces_before_forwarding_to_profiles
148
+ fake_worker_loop(@service)
149
+ raw_backtrace = mock('raw')
150
+ scrubbed_backtrace = mock('scrubbed')
151
+
152
+ thread = fake_thread(
153
+ :bucket => :agent,
154
+ :backtrace => raw_backtrace,
155
+ :scrubbed_backtrace => scrubbed_backtrace)
156
+
157
+ profile = @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
158
+ profile.expects(:aggregate).with(scrubbed_backtrace, :agent, thread)
159
+
160
+ @service.poll
161
+ end
162
+
163
+ def test_poll_records_supportability_metrics
164
+ fake_worker_loop(@service)
165
+
166
+ fake_thread(:bucket => :request)
167
+
168
+ profile = @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
169
+ profile.stubs(:aggregate)
170
+
171
+ @service.poll
172
+ @service.poll
173
+
174
+ # First poll doesn't record skew since we don't have a last poll time
175
+ assert_metrics_recorded({
176
+ 'Supportability/ThreadProfiler/PollingTime' => { :call_count => 2 },
177
+ 'Supportability/ThreadProfiler/Skew' => { :call_count => 1 }
178
+ })
179
+ end
180
+
181
+ def test_subscribe_adjusts_worker_loop_period
182
+ fake_worker_loop(@service)
183
+
184
+ @service.subscribe('foo', 'sample_period' => 10)
185
+ assert_has_period(10)
186
+
187
+ @service.subscribe('bar', 'sample_period' => 5)
188
+ assert_has_period(5)
189
+ end
190
+
191
+ def test_unsubscribe_adjusts_worker_loop_period
192
+ fake_worker_loop(@service)
193
+
194
+ @service.subscribe('foo', 'sample_period' => 10)
195
+ @service.subscribe('bar', 'sample_period' => 5)
196
+ @service.unsubscribe('bar')
197
+
198
+ assert_has_period(10)
199
+ end
200
+
201
+ def test_subscribe_sets_profile_agent_code
202
+ fake_worker_loop(@service)
203
+
204
+ @service.subscribe('foo', 'profile_agent_code' => true)
205
+ assert @service.profile_agent_code
206
+ end
207
+
208
+ def test_subscribe_sets_profile_agent_code_for_multiple_profiles
209
+ fake_worker_loop(@service)
210
+
211
+ @service.subscribe('foo', 'profile_agent_code' => true)
212
+ @service.subscribe('bar', 'profile_agent_code' => false)
213
+ assert @service.profile_agent_code
214
+ end
215
+
216
+ def test_subscribe_sets_profile_agent_code_when_missing
217
+ fake_worker_loop(@service)
218
+
219
+ @service.subscribe('foo')
220
+ @service.subscribe('bar')
221
+ assert !@service.profile_agent_code
222
+ end
223
+
224
+ def test_unsubscribe_sets_profile_agent_code
225
+ fake_worker_loop(@service)
226
+
227
+ @service.subscribe('foo', 'profile_agent_code' => true)
228
+ @service.subscribe('bar', 'profile_agent_code' => false)
229
+ assert @service.profile_agent_code
230
+
231
+ @service.unsubscribe('foo')
232
+ assert !@service.profile_agent_code
233
+ end
234
+
235
+ def test_sample_thread_does_not_backtrace_if_no_subscriptions
236
+ thread = fake_thread(:bucket => :request)
237
+ thread.expects(:backtrace).never
238
+ @service.sample_thread(thread)
239
+ end
240
+
241
+ def test_sample_thread_does_not_backtrace_if_ignored
242
+ thread = fake_thread(:bucket => :ignore)
243
+ thread.expects(:backtrace).never
244
+ @service.sample_thread(thread)
245
+ end
246
+
247
+ def test_sample_thread_does_not_backtrace_if_no_relevant_subscriptions
248
+ fake_worker_loop(@service)
249
+ @service.subscribe('foo')
250
+
251
+ thread = fake_thread(:bucket => :other)
252
+ thread.expects(:backtrace).never
253
+ @service.sample_thread(thread)
254
+ end
255
+
256
+ def test_on_transaction_finished_always_clears_buffer_for_current_thread
257
+ fake_worker_loop(@service)
258
+
259
+ thread = fake_thread(:bucket => :request)
260
+ @service.subscribe('foo')
261
+ @service.poll
262
+
263
+ fake_transaction_finished('bar', 0, 1, thread)
264
+ assert @service.buffer.empty?
265
+ end
266
+
267
+ def test_on_transaction_finished_aggregates_backtraces_to_subscribed_profile
268
+ fake_worker_loop(@service)
269
+
270
+ thread = fake_thread(:bucket => :request)
271
+ profile = @service.subscribe('foo')
272
+
273
+ t0 = Time.now
274
+ @service.poll
275
+
276
+ profile.expects(:aggregate).with(thread.backtrace, :request, thread)
277
+ fake_transaction_finished('foo', t0.to_f, 1, thread)
278
+ end
279
+
280
+ def test_on_transaction_finished_does_not_aggregate_backtraces_to_non_subscribed_profiles
281
+ fake_worker_loop(@service)
282
+
283
+ thread = fake_thread(:bucket => :request)
284
+ profile = @service.subscribe('foo')
285
+
286
+ t0 = Time.now
287
+ @service.poll
288
+
289
+ profile.expects(:aggregate).never
290
+ fake_transaction_finished('bar', t0.to_f, 1, thread)
291
+ end
292
+
293
+ def test_on_transaction_finished_delivers_buffered_backtraces_for_correct_thread
294
+ fake_worker_loop(@service)
295
+
296
+ thread0 = fake_thread(:bucket => :request)
297
+ thread1 = fake_thread(:bucket => :request)
298
+
299
+ profile = @service.subscribe('foo')
300
+
301
+ t0 = Time.now
302
+ @service.poll
303
+
304
+ profile.expects(:aggregate).with(thread0.backtrace, :request, thread0).once
305
+ profile.expects(:aggregate).with(thread1.backtrace, :request, thread1).once
306
+
307
+ fake_transaction_finished('foo', t0.to_f, 1, thread0)
308
+ fake_transaction_finished('foo', t0.to_f, 1, thread1)
309
+ end
310
+
311
+ def test_on_transaction_finished_only_delivers_backtraces_within_transaction_time_window
312
+ fake_worker_loop(@service)
313
+
314
+ thread = fake_thread(:bucket => :request)
315
+
316
+ profile = @service.subscribe('foo')
317
+
318
+ t0 = Time.now
319
+ 5.times do
320
+ @service.poll
321
+ advance_time(1.0)
322
+ end
323
+
324
+ profile.expects(:aggregate).with(thread.backtrace, :request, thread).times(2)
325
+ fake_transaction_finished('foo', (t0 + 1).to_f, 2.0, thread)
326
+ end
327
+
328
+ def test_does_not_deliver_non_request_backtraces_to_subscribed_profiles
329
+ fake_worker_loop(@service)
330
+
331
+ thread = fake_thread(:bucket => :other)
332
+
333
+ profile = @service.subscribe('foo')
334
+
335
+ t0 = Time.now
336
+ @service.poll
337
+
338
+ profile.expects(:aggregate).never
339
+ fake_transaction_finished('foo', t0.to_f, 1, thread)
340
+ end
341
+
342
+ def test_subscribe_sets_up_transaction_finished_subscription
343
+ fake_worker_loop(@service)
344
+
345
+ Thread.current[:bucket] = :request
346
+ FakeThread.list << Thread.current
347
+
348
+ profile = @service.subscribe('foo')
349
+
350
+ t0 = Time.now
351
+ @service.poll
352
+
353
+ profile.expects(:aggregate).once
354
+ fake_transaction_finished('foo', t0.to_f, 1.0)
355
+ ensure
356
+ Thread.current[:bucket] = nil
357
+ end
358
+
359
+ def test_service_increments_profile_poll_counts
360
+ fake_worker_loop(@service)
361
+
362
+ profile = @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
363
+ 5.times { @service.poll }
364
+ assert_equal(5, profile.poll_count)
365
+
366
+ @service.unsubscribe(BacktraceService::ALL_TRANSACTIONS)
367
+ 5.times { @service.poll }
368
+ assert_equal(5, profile.poll_count)
369
+ end
370
+
371
+ def test_poll_scrubs_dead_threads_from_buffer
372
+ fake_worker_loop(@service)
373
+ thread0 = fake_thread(:bucket => :request)
374
+ thread1 = fake_thread(:bucket => :request)
375
+
376
+ @service.subscribe('foo')
377
+ @service.poll
378
+
379
+ thread1.stubs(:alive?).returns(false)
380
+ @service.poll
381
+
382
+ assert_equal(2, @service.buffer[thread0].size)
383
+ assert_nil(@service.buffer[thread1])
384
+
385
+ @service.unsubscribe('foo')
386
+ end
387
+
388
+ def test_poll_records_polling_time
389
+ fake_worker_loop(@service)
390
+
391
+ profile = @service.subscribe('foo')
392
+ def profile.increment_poll_count
393
+ advance_time(5.0)
394
+ end
395
+
396
+ @service.poll
397
+
398
+ expected = { :call_count => 1, :total_call_time => 5 }
399
+ assert_metrics_recorded(
400
+ { 'Supportability/ThreadProfiler/PollingTime' => expected }
401
+ )
402
+ end
403
+
404
+ def test_buffer_backtrace_for_thread_should_limit_buffer_size
405
+ fake_worker_loop(@service)
406
+
407
+ @service.subscribe('foo')
408
+
409
+ thread = stub
410
+ BacktraceService::MAX_BUFFER_LENGTH.times do
411
+ @service.buffer_backtrace_for_thread(thread, Time.now.to_f, stub, :request)
412
+ end
413
+ assert_equal BacktraceService::MAX_BUFFER_LENGTH, @service.buffer[thread].length
414
+
415
+ @service.buffer_backtrace_for_thread(thread, Time.now.to_f, stub, :request)
416
+ assert_equal BacktraceService::MAX_BUFFER_LENGTH, @service.buffer[thread].length
417
+ assert_metrics_recorded(["Supportability/XraySessions/DroppedBacktraces"])
418
+ end
419
+
420
+ def test_dynamically_adjusts_worker_loop_period
421
+ fake_worker_loop(@service)
422
+
423
+ poll_for(0.01)
424
+ assert_has_period 0.2
425
+ end
426
+
427
+ def test_doesnt_adjust_worker_loop_period_if_not_enough_overhead
428
+ fake_worker_loop(@service)
429
+
430
+ poll_for(0.001)
431
+ assert_has_default_period
432
+ end
433
+
434
+ def test_dynamic_adjustment_returns_if_later_polls_are_shorter
435
+ fake_worker_loop(@service)
436
+
437
+ poll_for(0.01)
438
+ assert_has_period 0.2
439
+
440
+ poll_for(0.001)
441
+ assert_has_default_period
442
+ end
443
+
444
+ def test_dynamic_adjustment_drives_from_config
445
+ freeze_time
446
+ fake_worker_loop(@service)
447
+
448
+ with_config(:'xray_session.max_profile_overhead' => 0.1) do
449
+ poll_for(0.01)
450
+ assert_has_default_period
451
+ end
452
+ end
453
+
454
+ def poll_for(poll_length)
455
+ fake_last_poll_took(poll_length)
456
+ @service.poll
457
+ end
458
+
459
+ def assert_has_period(value)
460
+ assert_in_delta(value, @service.worker_loop.period, 0.001)
461
+ end
462
+
463
+ def assert_has_default_period
464
+ assert_equal 0.1, @service.worker_loop.period
465
+ end
466
+
467
+ def fake_last_poll_took(last_poll_length)
468
+ # We need to adjust Time.now during the midst of poll
469
+ # Slip in before the adjust_polling_time call to advance the clock
470
+ @service.define_singleton_method(:adjust_polling_time) do |end_time, *args|
471
+ end_time += last_poll_length
472
+ super(end_time, *args)
473
+ end
474
+ end
475
+
476
+ def fake_transaction_finished(name, start_timestamp, duration, thread=nil)
477
+ payload = {
478
+ :name => name,
479
+ :start_timestamp => start_timestamp,
480
+ :duration => duration
481
+ }
482
+ payload[:thread] = thread if thread
483
+ @event_listener.notify(:transaction_finished, payload)
484
+ end
485
+
486
+ def fake_worker_loop(service)
487
+ dummy_loop = NewRelic::Agent::WorkerLoop.new
488
+ dummy_loop.stubs(:run).returns(nil)
489
+ dummy_loop.stubs(:stop).returns(nil)
490
+ service.stubs(:worker_loop).returns(dummy_loop)
491
+ service.effective_polling_period = 0.1
492
+ dummy_loop
493
+ end
494
+
495
+ def fake_thread(opts={})
496
+ defaults = {
497
+ :backtrace => mock('backtrace')
498
+ }
499
+ thread = FakeThread.new(defaults.merge(opts))
500
+ FakeThread.list << thread
501
+ thread
502
+ end
503
+ end
504
+
505
+ # These tests do not use ThreadedTestCase as FakeThread is synchronous and
506
+ # prevents the detection of concurrency issues.
507
+ class BacktraceServiceConcurrencyTest < Test::Unit::TestCase
508
+ def setup
509
+ NewRelic::Agent.instance.stats_engine.clear_stats
510
+ @service = BacktraceService.new
511
+ end
512
+
513
+ def teardown
514
+ @service.stop
515
+ end
516
+
517
+ def test_adding_subscriptions_is_thread_safe
518
+ @service.worker_loop.propagate_errors = true
519
+
520
+ @service.subscribe('foo', { 'sample_period' => 0.01 })
521
+
522
+ 10000.times do
523
+ @service.subscribe(BacktraceService::ALL_TRANSACTIONS)
524
+ @service.unsubscribe(BacktraceService::ALL_TRANSACTIONS)
525
+ end
526
+
527
+ @service.unsubscribe('foo')
528
+
529
+ @service.worker_thread.join
530
+ end
531
+ end
532
+
533
+ end
534
+ else
535
+ module NewRelic::Agent::Threading
536
+ class BacktraceServiceUnsupportedTest < Test::Unit::TestCase
537
+ def test_is_not_supported?
538
+ assert_false BacktraceService.is_supported?
539
+ end
540
+
541
+ def test_safely_ignores_subscribe
542
+ service = BacktraceService.new
543
+ service.subscribe('fine/ignore/me')
544
+
545
+ assert_false service.subscribed?('fine/ignore/me')
546
+ end
547
+
548
+ def test_safely_ignores_unsubscribe
549
+ service = BacktraceService.new
550
+
551
+ service.subscribe('fine/ignore/me')
552
+ service.unsubscribe('fine/ignore/me')
553
+
554
+ assert_false service.subscribed?('fine/ignore/me')
555
+ end
556
+
557
+ def test_cannot_start
558
+ service = BacktraceService.new
559
+
560
+ service.start
561
+
562
+ assert_false service.running?
563
+ end
564
+
565
+ end
566
+ end
567
+ end