ghazel-newrelic_rpm 3.4.0.2 → 3.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. data/.gitignore +21 -0
  2. data/.project +23 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +180 -1
  5. data/GUIDELINES_FOR_CONTRIBUTING.md +73 -0
  6. data/Gemfile +16 -0
  7. data/InstallationNotes.md +15 -0
  8. data/LICENSE +1 -1
  9. data/{README.rdoc → README.md} +71 -55
  10. data/Rakefile +54 -0
  11. data/config.dot +290 -0
  12. data/config/database.yml +5 -0
  13. data/init.rb +38 -0
  14. data/lib/new_relic/agent.rb +9 -4
  15. data/lib/new_relic/agent/agent.rb +189 -230
  16. data/lib/new_relic/agent/beacon_configuration.rb +34 -48
  17. data/lib/new_relic/agent/browser_monitoring.rb +108 -61
  18. data/lib/new_relic/agent/busy_calculator.rb +12 -4
  19. data/lib/new_relic/agent/configuration.rb +49 -0
  20. data/lib/new_relic/agent/configuration/defaults.rb +89 -0
  21. data/lib/new_relic/agent/configuration/environment_source.rb +56 -0
  22. data/lib/new_relic/agent/configuration/manager.rb +116 -0
  23. data/lib/new_relic/agent/configuration/server_source.rb +27 -0
  24. data/lib/new_relic/agent/configuration/yaml_source.rb +61 -0
  25. data/lib/new_relic/agent/database.rb +37 -22
  26. data/lib/new_relic/agent/error_collector.rb +47 -43
  27. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -5
  28. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +27 -6
  29. data/lib/new_relic/agent/instrumentation/data_mapper.rb +2 -10
  30. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/memcache.rb +2 -2
  32. data/lib/new_relic/agent/instrumentation/metric_frame.rb +4 -14
  33. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +4 -18
  34. data/lib/new_relic/agent/instrumentation/rack.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +3 -3
  36. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +2 -2
  37. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +5 -1
  38. data/lib/new_relic/agent/instrumentation/resque.rb +1 -1
  39. data/lib/new_relic/agent/instrumentation/sinatra.rb +14 -10
  40. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +4 -3
  41. data/lib/new_relic/agent/method_tracer.rb +5 -1
  42. data/lib/new_relic/agent/new_relic_service.rb +231 -61
  43. data/lib/new_relic/agent/pipe_channel_manager.rb +37 -23
  44. data/lib/new_relic/agent/pipe_service.rb +5 -1
  45. data/lib/new_relic/agent/samplers/delayed_job_sampler.rb +2 -5
  46. data/lib/new_relic/agent/samplers/memory_sampler.rb +2 -2
  47. data/lib/new_relic/agent/sql_sampler.rb +44 -68
  48. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +40 -24
  49. data/lib/new_relic/agent/stats_engine/metric_stats.rb +89 -14
  50. data/lib/new_relic/agent/stats_engine/samplers.rb +7 -3
  51. data/lib/new_relic/agent/stats_engine/transactions.rb +19 -11
  52. data/lib/new_relic/agent/thread.rb +27 -0
  53. data/lib/new_relic/agent/thread_profiler.rb +295 -0
  54. data/lib/new_relic/agent/transaction_info.rb +24 -4
  55. data/lib/new_relic/agent/transaction_sample_builder.rb +11 -11
  56. data/lib/new_relic/agent/transaction_sampler.rb +51 -61
  57. data/lib/new_relic/agent/worker_loop.rb +29 -15
  58. data/lib/new_relic/collection_helper.rb +1 -1
  59. data/lib/new_relic/commands/deployments.rb +19 -10
  60. data/lib/new_relic/control.rb +0 -1
  61. data/lib/new_relic/control/class_methods.rb +2 -3
  62. data/lib/new_relic/control/frameworks/rails.rb +24 -18
  63. data/lib/new_relic/control/frameworks/rails3.rb +18 -1
  64. data/lib/new_relic/control/frameworks/ruby.rb +2 -2
  65. data/lib/new_relic/control/instance_methods.rb +36 -53
  66. data/lib/new_relic/control/logging_methods.rb +5 -23
  67. data/lib/new_relic/control/server_methods.rb +11 -13
  68. data/lib/new_relic/delayed_job_injection.rb +1 -1
  69. data/lib/new_relic/helper.rb +13 -0
  70. data/lib/new_relic/language_support.rb +19 -22
  71. data/lib/new_relic/local_environment.rb +2 -3
  72. data/lib/new_relic/metric_data.rb +10 -2
  73. data/lib/new_relic/metric_spec.rb +6 -2
  74. data/lib/new_relic/noticed_error.rb +24 -9
  75. data/lib/new_relic/rack.rb +4 -0
  76. data/lib/new_relic/rack/browser_monitoring.rb +28 -10
  77. data/lib/new_relic/rack/developer_mode.rb +3 -0
  78. data/lib/new_relic/rack/error_collector.rb +56 -0
  79. data/lib/new_relic/transaction_sample.rb +23 -13
  80. data/lib/new_relic/transaction_sample/segment.rb +13 -15
  81. data/lib/new_relic/version.rb +3 -3
  82. data/lib/tasks/tests.rake +8 -8
  83. data/newrelic.yml +15 -32
  84. data/newrelic_rpm.gemspec +158 -38
  85. data/newrelic_rpm.gemspec.erb +55 -0
  86. data/test/config/newrelic.yml +3 -2
  87. data/test/intentional_fail.rb +10 -0
  88. data/test/multiverse/.gitignore +10 -0
  89. data/test/multiverse/README.md +90 -0
  90. data/test/multiverse/Rakefile +17 -0
  91. data/test/multiverse/lib/multiverse/color.rb +13 -0
  92. data/test/multiverse/lib/multiverse/envfile.rb +66 -0
  93. data/test/multiverse/lib/multiverse/environment.rb +16 -0
  94. data/test/multiverse/lib/multiverse/output_collector.rb +29 -0
  95. data/test/multiverse/lib/multiverse/runner.rb +44 -0
  96. data/test/multiverse/lib/multiverse/suite.rb +162 -0
  97. data/test/multiverse/script/run_one +3 -0
  98. data/test/multiverse/script/runner +9 -0
  99. data/test/multiverse/suites/active_record/Envfile +13 -0
  100. data/test/multiverse/suites/active_record/ar_method_aliasing.rb +94 -0
  101. data/test/multiverse/suites/active_record/config/newrelic.yml +22 -0
  102. data/test/multiverse/suites/active_record/encoding_test.rb +26 -0
  103. data/test/multiverse/suites/agent_only/Envfile +3 -0
  104. data/test/multiverse/suites/agent_only/config/newrelic.yml +22 -0
  105. data/test/multiverse/suites/agent_only/http_response_code_test.rb +53 -0
  106. data/test/multiverse/suites/agent_only/marshaling_test.rb +109 -0
  107. data/test/multiverse/suites/agent_only/method_visibility_test.rb +98 -0
  108. data/test/multiverse/suites/agent_only/pipe_manager_test.rb +33 -0
  109. data/test/multiverse/suites/agent_only/service_timeout_test.rb +29 -0
  110. data/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb +30 -0
  111. data/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb +32 -0
  112. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +80 -0
  113. data/test/multiverse/suites/datamapper/Envfile +8 -0
  114. data/test/multiverse/suites/datamapper/config/newrelic.yml +22 -0
  115. data/test/multiverse/suites/datamapper/encoding_test.rb +36 -0
  116. data/test/multiverse/suites/monitor_mode_false/Envfile +2 -0
  117. data/test/multiverse/suites/monitor_mode_false/config/newrelic.yml +25 -0
  118. data/test/multiverse/suites/monitor_mode_false/no_dns_resolv.rb +29 -0
  119. data/test/multiverse/suites/no_load/Envfile +2 -0
  120. data/test/multiverse/suites/no_load/config/newrelic.yml +23 -0
  121. data/test/multiverse/suites/no_load/start_up_test.rb +14 -0
  122. data/test/multiverse/suites/rails_3_error_tracing/Envfile +15 -0
  123. data/test/multiverse/suites/rails_3_error_tracing/config/newrelic.yml +165 -0
  124. data/test/multiverse/suites/rails_3_error_tracing/error_tracing_test.rb +236 -0
  125. data/test/multiverse/suites/rails_3_gc/Envfile +8 -0
  126. data/test/multiverse/suites/rails_3_gc/config/newrelic.yml +167 -0
  127. data/test/multiverse/suites/rails_3_gc/instrumentation_test.rb +92 -0
  128. data/test/multiverse/suites/rails_3_queue_time/Envfile +15 -0
  129. data/test/multiverse/suites/rails_3_queue_time/config/newrelic.yml +165 -0
  130. data/test/multiverse/suites/rails_3_queue_time/queue_time_test.rb +75 -0
  131. data/test/multiverse/suites/rails_3_views/.gitignore +3 -0
  132. data/test/multiverse/suites/rails_3_views/Envfile +16 -0
  133. data/test/multiverse/suites/rails_3_views/app/views/foos/_foo.html.haml +1 -0
  134. data/test/multiverse/suites/rails_3_views/app/views/test/_a_partial.html.erb +1 -0
  135. data/test/multiverse/suites/rails_3_views/app/views/test/_mid_partial.html.erb +1 -0
  136. data/test/multiverse/suites/rails_3_views/app/views/test/_top_partial.html.erb +3 -0
  137. data/test/multiverse/suites/rails_3_views/app/views/test/deep_partial.html.erb +3 -0
  138. data/test/multiverse/suites/rails_3_views/app/views/test/haml_view.html.haml +6 -0
  139. data/test/multiverse/suites/rails_3_views/app/views/test/index.html.erb +4 -0
  140. data/test/multiverse/suites/rails_3_views/config/newrelic.yml +164 -0
  141. data/test/multiverse/suites/rails_3_views/view_instrumentation_test.rb +245 -0
  142. data/test/multiverse/suites/resque/Envfile +21 -0
  143. data/test/multiverse/suites/resque/config/newrelic.yml +22 -0
  144. data/test/multiverse/suites/resque/dump.rdb +0 -0
  145. data/test/multiverse/suites/resque/instrumentation_test.rb +73 -0
  146. data/test/multiverse/suites/rum_auto_instrumentation/Envfile +4 -0
  147. data/test/multiverse/suites/rum_auto_instrumentation/config/newrelic.yml +24 -0
  148. data/test/multiverse/suites/rum_auto_instrumentation/problem_response.html +422 -0
  149. data/test/multiverse/suites/rum_auto_instrumentation/responses/worst_case_small.html +5000 -0
  150. data/test/multiverse/suites/rum_auto_instrumentation/sanity_test.rb +115 -0
  151. data/test/multiverse/suites/sinatra/Envfile +13 -0
  152. data/test/multiverse/suites/sinatra/config/newrelic.yml +24 -0
  153. data/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb +76 -0
  154. data/test/multiverse/suites/sinatra/sinatra_routes_test.rb +46 -0
  155. data/test/multiverse/test/multiverse_test.rb +55 -0
  156. data/test/multiverse/test/suite_examples/one/a/Envfile +3 -0
  157. data/test/multiverse/test/suite_examples/one/a/a_test.rb +11 -0
  158. data/test/multiverse/test/suite_examples/one/a/config/newrelic.yml +24 -0
  159. data/test/multiverse/test/suite_examples/one/b/Envfile +3 -0
  160. data/test/multiverse/test/suite_examples/one/b/b_test.rb +11 -0
  161. data/test/multiverse/test/suite_examples/one/b/config/newrelic.yml +24 -0
  162. data/test/multiverse/test/suite_examples/three/a/Envfile +2 -0
  163. data/test/multiverse/test/suite_examples/three/a/fail_test.rb +6 -0
  164. data/test/multiverse/test/suite_examples/three/b/Envfile +2 -0
  165. data/test/multiverse/test/suite_examples/three/b/win_test.rb +6 -0
  166. data/test/multiverse/test/suite_examples/two/a/Envfile +1 -0
  167. data/test/multiverse/test/suite_examples/two/a/fail_test.rb +6 -0
  168. data/test/new_relic/agent/agent/connect_test.rb +134 -164
  169. data/test/new_relic/agent/agent/start_test.rb +111 -81
  170. data/test/new_relic/agent/agent/start_worker_thread_test.rb +6 -33
  171. data/test/new_relic/agent/agent_test.rb +88 -9
  172. data/test/new_relic/agent/agent_test_controller.rb +1 -1
  173. data/test/new_relic/agent/agent_test_controller_test.rb +42 -10
  174. data/test/new_relic/agent/beacon_configuration_test.rb +63 -67
  175. data/test/new_relic/agent/browser_monitoring_test.rb +150 -79
  176. data/test/new_relic/agent/configuration/environment_source_test.rb +74 -0
  177. data/test/new_relic/agent/configuration/manager_test.rb +149 -0
  178. data/test/new_relic/agent/configuration/server_source_test.rb +45 -0
  179. data/test/new_relic/agent/configuration/yaml_source_test.rb +56 -0
  180. data/test/new_relic/agent/error_collector/notice_error_test.rb +63 -50
  181. data/test/new_relic/agent/error_collector_test.rb +22 -12
  182. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +21 -11
  183. data/test/new_relic/agent/instrumentation/metric_frame_test.rb +6 -0
  184. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +2 -2
  185. data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +5 -5
  186. data/test/new_relic/agent/method_tracer_test.rb +6 -6
  187. data/test/new_relic/agent/mock_scope_listener.rb +3 -0
  188. data/test/new_relic/agent/new_relic_service_test.rb +208 -23
  189. data/test/new_relic/agent/pipe_channel_manager_test.rb +34 -17
  190. data/test/new_relic/agent/rpm_agent_test.rb +27 -23
  191. data/test/new_relic/agent/sql_sampler_test.rb +81 -56
  192. data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +3 -20
  193. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +12 -1
  194. data/test/new_relic/agent/stats_engine_test.rb +17 -8
  195. data/test/new_relic/agent/thread_profiler_test.rb +536 -0
  196. data/test/new_relic/agent/thread_test.rb +76 -0
  197. data/test/new_relic/agent/threaded_test.rb +65 -0
  198. data/test/new_relic/agent/transaction_info_test.rb +45 -4
  199. data/test/new_relic/agent/transaction_sample_builder_test.rb +8 -6
  200. data/test/new_relic/agent/transaction_sampler_test.rb +193 -204
  201. data/test/new_relic/agent/worker_loop_test.rb +20 -0
  202. data/test/new_relic/agent_test.rb +69 -41
  203. data/test/new_relic/collection_helper_test.rb +7 -8
  204. data/test/new_relic/command/deployments_test.rb +18 -2
  205. data/test/new_relic/control/frameworks/rails_test.rb +26 -0
  206. data/test/new_relic/control/logging_methods_test.rb +78 -52
  207. data/test/new_relic/control_test.rb +91 -129
  208. data/test/new_relic/fake_collector.rb +103 -31
  209. data/test/new_relic/fake_service.rb +8 -2
  210. data/test/new_relic/load_test.rb +13 -0
  211. data/test/new_relic/local_environment_test.rb +7 -10
  212. data/test/new_relic/metric_data_test.rb +45 -16
  213. data/test/new_relic/noticed_error_test.rb +14 -0
  214. data/test/new_relic/rack/browser_monitoring_test.rb +15 -9
  215. data/test/new_relic/rack/developer_mode_test.rb +13 -7
  216. data/test/new_relic/rack/error_collector_test.rb +74 -0
  217. data/test/new_relic/transaction_sample/segment_test.rb +23 -4
  218. data/test/new_relic/transaction_sample_test.rb +47 -2
  219. data/test/script/build_test_gem.sh +9 -3
  220. data/test/script/ci.sh +48 -21
  221. data/test/script/ci_multiverse_runner.sh +11 -11
  222. data/test/test_helper.rb +37 -18
  223. data/ui/helpers/developer_mode_helper.rb +21 -11
  224. data/ui/views/layouts/newrelic_default.rhtml +1 -0
  225. data/ui/views/newrelic/show_sample.rhtml +1 -1
  226. data/ui/views/newrelic/threads.rhtml +2 -10
  227. data/vendor/gems/metric_parser-0.1.0.pre1/.specification +116 -0
  228. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet_init.rb +7 -0
  229. metadata +132 -58
  230. data/lib/new_relic/control/configuration.rb +0 -206
  231. data/test/new_relic/control/configuration_test.rb +0 -77
@@ -8,34 +8,82 @@ module NewRelic
8
8
  # A simple mutex-synchronized hash to make sure our statistics
9
9
  # are internally consistent even in truly-threaded rubies like JRuby
10
10
  class SynchronizedHash < ::Hash
11
- include NewRelic::LanguageSupport::SynchronizedHash
12
-
11
+ attr_reader :lock
12
+
13
13
  def initialize
14
14
  @lock = Mutex.new
15
15
  end
16
-
16
+
17
+ def initialize_copy(old)
18
+ super
19
+ old.each do |key, value|
20
+ self.store(key, value.dup)
21
+ end
22
+ end
23
+
17
24
  def []=(*args)
18
25
  @lock.synchronize { super }
26
+ rescue => e
27
+ log_error(e)
19
28
  end
20
29
 
21
30
  def clear(*args)
22
31
  @lock.synchronize { super }
32
+ rescue => e
33
+ log_error(e)
23
34
  end
24
35
 
25
36
  def delete(*args)
26
37
  @lock.synchronize { super }
38
+ rescue => e
39
+ log_error(e)
27
40
  end
28
41
 
29
42
  def delete_if(*args)
30
43
  @lock.synchronize { super }
44
+ rescue => e
45
+ log_error(e)
46
+ end
47
+
48
+ def reset
49
+ values.each { |s| s.reset }
50
+ end
51
+
52
+ def log_error(e)
53
+ backtraces = Thread.list.map { |t| log_thread(t) }.join("\n\n")
54
+ NewRelic::Control.instance.log.warn(
55
+ "SynchronizedHash failure: #{e.class.name}: #{e.message}\n#{backtraces}")
56
+ end
57
+
58
+ def log_thread(t)
59
+ # Ruby 1.8 doesn't expose backtrace properly, so make sure it's there
60
+ if t.nil? || !t.respond_to?(:backtrace) || t.backtrace.nil?
61
+ return "#{t}\n\tNo backtrace for thread"
62
+ end
63
+
64
+ backtrace = t.backtrace.map { |b| "\t#{b}" }.join("\n")
65
+ "\t#{t}\n#{backtrace}"
66
+
67
+ rescue Exception => e
68
+ # JRuby 1.7.0 has a nasty habit of raising a
69
+ # java.lang.NullPointerException when we iterate through threads
70
+ # asking for backtraces. This line allows us to swallow java
71
+ # exceptions without referencing their classes (since they don't
72
+ # exist in MRI). It also prevents us from swallowing signals or
73
+ # other nasty things that can happen when you rescue Exception.
74
+ NewRelic::Control.instance.log.warn(
75
+ "Error collecting thread backtraces: #{e.class.name}: #{e.message}")
76
+ NewRelic::Control.instance.log.debug( e.backtrace.join("\n") )
77
+
78
+ raise e if e.class.ancestors.include? Exception
31
79
  end
32
80
  end
33
-
81
+
34
82
  # Returns all of the metric names of all the stats in the engine
35
83
  def metrics
36
84
  stats_hash.keys.map(&:to_s)
37
85
  end
38
-
86
+
39
87
  # a simple accessor for looking up a stat with no scope -
40
88
  # returns a new stats object if no stats object for that
41
89
  # metric exists yet
@@ -64,17 +112,43 @@ module NewRelic
64
112
  end
65
113
  stats
66
114
  end
67
-
115
+
68
116
  # Returns a stat if one exists, otherwise returns nil. If you
69
117
  # want auto-initialization, use one of get_stats or get_stats_no_scope
70
118
  def lookup_stats(metric_name, scope_name = '')
71
119
  stats_hash[NewRelic::MetricSpec.new(metric_name, scope_name)]
72
120
  end
73
-
121
+
122
+
123
+ # Helper method for timing supportability metrics
124
+ def record_supportability_metrics_timed(metrics)
125
+ start_time = Time.now
126
+ yield
127
+ end_time = Time.now
128
+ duration = (end_time - start_time).to_f
129
+ ensure
130
+ record_supportability_metrics(duration, metrics) do |value, metric|
131
+ metric.record_data_point(value)
132
+ end
133
+ end
134
+
135
+ # Helper for recording a straight value into the count
136
+ def record_supportability_metrics_count(value, *metrics)
137
+ record_supportability_metrics(value, *metrics) do |value, metric|
138
+ metric.call_count = value
139
+ end
140
+ end
141
+
142
+ # Helper method for recording supportability metrics consistently
143
+ def record_supportability_metrics(value, *metrics)
144
+ metrics.each do |metric|
145
+ yield(value, get_stats_no_scope("Supportability/#{metric}"))
146
+ end
147
+ end
148
+
74
149
  # This module was extracted from the harvest method and should
75
150
  # be refactored
76
151
  module Harvest
77
-
78
152
  # merge data from previous harvests into this stats engine -
79
153
  # takes into account the case where there are new stats for
80
154
  # that metric, and the case where there is no current data
@@ -135,10 +209,12 @@ module NewRelic
135
209
 
136
210
  def merge_stats(other_engine_or_hash, metric_ids)
137
211
  old_data = get_stats_hash_from(other_engine_or_hash)
138
-
139
212
  timeslice_data = {}
140
- stats_hash.each do | metric_spec, stats |
141
-
213
+ stats_hash.lock.synchronize do
214
+ Thread.current['newrelic_stats_hash'] = stats_hash.clone
215
+ stats_hash.reset
216
+ end
217
+ Thread.current['newrelic_stats_hash'].each do |metric_spec, stats|
142
218
  metric_spec = coerce_to_metric_spec(metric_spec)
143
219
  stats_copy = clone_and_reset_stats(metric_spec, stats)
144
220
  merge_old_data!(metric_spec, stats_copy, old_data)
@@ -160,7 +236,6 @@ module NewRelic
160
236
  # sacrificing efficiency.
161
237
  # +++
162
238
  def harvest_timeslice_data(previous_timeslice_data, metric_ids)
163
-
164
239
  poll harvest_samplers
165
240
  merge_stats(previous_timeslice_data, metric_ids)
166
241
  end
@@ -173,9 +248,9 @@ module NewRelic
173
248
 
174
249
  # Reset each of the stats, such as when a new passenger instance starts up.
175
250
  def reset_stats
176
- stats_hash.values.each { |s| s.reset }
251
+ stats_hash.reset
177
252
  end
178
-
253
+
179
254
  # returns a memoized SynchronizedHash that holds the actual
180
255
  # instances of Stats keyed off their MetricName
181
256
  def stats_hash
@@ -27,15 +27,19 @@ module Agent
27
27
  # start up a thread that will periodically poll for metric samples
28
28
  return if periodic_samplers.empty?
29
29
 
30
- @sampler_thread = Thread.new do
31
- while true do
30
+ @sampler_thread = NewRelic::Agent::Thread.new('Sampler Tasks') do
31
+ loop do
32
+ now = Time.now
32
33
  begin
33
34
  sleep POLL_PERIOD
34
35
  poll periodic_samplers
36
+ ensure
37
+ NewRelic::Agent.instance.stats_engine \
38
+ .get_stats_no_scope('Supportability/Samplers') \
39
+ .record_data_point((Time.now - now).to_f)
35
40
  end
36
41
  end
37
42
  end
38
- @sampler_thread['newrelic_label'] = 'Sampler Tasks'
39
43
  end
40
44
 
41
45
  private
@@ -13,11 +13,11 @@ module Agent
13
13
  @children_time = 0
14
14
  end
15
15
  end
16
-
16
+
17
17
  # Handles pushing and popping elements onto an internal stack that
18
18
  # tracks where time should be allocated in Transaction Traces
19
19
  module Transactions
20
-
20
+
21
21
  # Defines methods that stub out the stats engine methods
22
22
  # when the agent is disabled
23
23
  module Shim # :nodoc:
@@ -29,29 +29,29 @@ module Agent
29
29
  def scope_name; end
30
30
  def pop_scope(*args); end
31
31
  end
32
-
32
+
33
33
  # add a new transaction sampler, unless we're currently in a
34
34
  # transaction (then we fail)
35
35
  def transaction_sampler= sampler
36
36
  fail "Can't add a scope listener midflight in a transaction" if scope_stack.any?
37
37
  @transaction_sampler = sampler
38
38
  end
39
-
39
+
40
40
  # removes a transaction sampler
41
41
  def remove_transaction_sampler(l)
42
42
  @transaction_sampler = nil
43
43
  end
44
-
44
+
45
45
  # Pushes a scope onto the transaction stack - this generates a
46
46
  # TransactionSample::Segment at the end of transaction execution
47
47
  def push_scope(metric, time = Time.now.to_f, deduct_call_time_from_parent = true)
48
48
  stack = scope_stack
49
- @transaction_sampler.notice_push_scope metric, time if @transaction_sampler
49
+ @transaction_sampler.notice_push_scope metric, time if sampler_enabled?
50
50
  scope = ScopeStackElement.new(metric, deduct_call_time_from_parent)
51
51
  stack.push scope
52
52
  scope
53
53
  end
54
-
54
+
55
55
  # Pops a scope off the transaction stack - this updates the
56
56
  # transaction sampler that we've finished execution of a traced method
57
57
  def pop_scope(expected_scope, duration, time=Time.now.to_f)
@@ -66,10 +66,14 @@ module Agent
66
66
  stack.last.children_time += scope.children_time
67
67
  end
68
68
  end
69
- @transaction_sampler.notice_pop_scope(scope.name, time) if @transaction_sampler
69
+ @transaction_sampler.notice_pop_scope(scope.name, time) if sampler_enabled?
70
70
  scope
71
71
  end
72
72
 
73
+ def sampler_enabled?
74
+ @transaction_sampler && Agent.config[:'transaction_tracer.enabled']
75
+ end
76
+
73
77
  # Returns the latest ScopeStackElement
74
78
  def peek_scope
75
79
  scope_stack.last
@@ -86,7 +90,7 @@ module Agent
86
90
  def scope_name=(transaction)
87
91
  Thread::current[:newrelic_scope_name] = transaction
88
92
  end
89
-
93
+
90
94
  # Returns the current scope name from the thread local
91
95
  def scope_name
92
96
  Thread::current[:newrelic_scope_name]
@@ -104,7 +108,11 @@ module Agent
104
108
  # and is ignored.
105
109
  #
106
110
  def end_transaction
107
- GCProfiler.capture
111
+ elapsed = GCProfiler.capture
112
+ if @transaction_sampler && @transaction_sampler.last_sample
113
+ @transaction_sampler.last_sample.params[:custom_params] ||= {}
114
+ @transaction_sampler.last_sample.params[:custom_params][:gc_time] = elapsed
115
+ end
108
116
  stack = scope_stack
109
117
 
110
118
  if stack && stack.empty?
@@ -114,7 +122,7 @@ module Agent
114
122
  end
115
123
 
116
124
  private
117
-
125
+
118
126
  # Returns the current scope stack, memoized to a thread local variable
119
127
  def scope_stack
120
128
  Thread::current[:newrelic_scope_stack] ||= []
@@ -0,0 +1,27 @@
1
+ module NewRelic
2
+ module Agent
3
+
4
+ class Thread < ::Thread
5
+ def initialize(label)
6
+ NewRelic::Agent.logger.debug("Creating New Relic thread: #{label}")
7
+ self[:newrelic_label] = label
8
+ super
9
+ end
10
+
11
+ def self.bucket_thread(thread, profile_agent_code)
12
+ if thread.key?(:newrelic_label)
13
+ return profile_agent_code ? :agent : :ignore
14
+ elsif !thread[:newrelic_metric_frame].nil?
15
+ thread[:newrelic_metric_frame].request.nil? ? :background : :request
16
+ else
17
+ :other
18
+ end
19
+ end
20
+
21
+ def self.scrub_backtrace(thread, profile_agent_code)
22
+ return thread.backtrace if profile_agent_code
23
+ thread.backtrace.select {|t| t !~ /\/newrelic_rpm-\d/ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,295 @@
1
+ require 'new_relic/agent/thread'
2
+ require 'new_relic/agent/worker_loop'
3
+
4
+ module NewRelic
5
+ module Agent
6
+
7
+ class ThreadProfiler
8
+
9
+ attr_reader :profile
10
+
11
+ def self.is_supported?
12
+ RUBY_VERSION >= "1.9.2"
13
+ end
14
+
15
+ def start(profile_id, duration, interval, profile_agent_code)
16
+ if !ThreadProfiler.is_supported?
17
+ log.debug("Not starting thread profile as it isn't supported on this environment")
18
+ @profile = nil
19
+ else
20
+ log.debug("Starting thread profile. profile_id=#{profile_id}, duration=#{duration}")
21
+ @profile = ThreadProfile.new(profile_id, duration, interval, profile_agent_code)
22
+ @profile.run
23
+ end
24
+ end
25
+
26
+ def stop(report_data)
27
+ @profile.stop unless @profile.nil?
28
+ @profile = nil if !report_data
29
+ end
30
+
31
+ def harvest
32
+ profile = @profile
33
+ @profile = nil
34
+ profile
35
+ end
36
+
37
+ def respond_to_commands(commands, &notify_results)
38
+ return if commands.empty? || commands.first.size < 2
39
+
40
+ # Doesn't deal with multiple commands in the return set as
41
+ # we currently only have start/stop of thread profiling
42
+ command_id = commands.first[0]
43
+ command = commands.first[1]
44
+
45
+ name = command["name"]
46
+ arguments = command["arguments"]
47
+
48
+ case name
49
+ when "start_profiler"
50
+ start_unless_running_and_notify(command_id, arguments, &notify_results)
51
+
52
+ when "stop_profiler"
53
+ stop_and_notify(command_id, arguments, &notify_results)
54
+ end
55
+ end
56
+
57
+ def running?
58
+ !@profile.nil?
59
+ end
60
+
61
+ def finished?
62
+ @profile && @profile.finished?
63
+ end
64
+
65
+ private
66
+
67
+ def start_unless_running_and_notify(command_id, arguments)
68
+ profile_id = arguments.fetch("profile_id", -1)
69
+ duration = arguments.fetch("duration", 120)
70
+ interval = arguments.fetch("sample_period", 0.1)
71
+ profile_agent_code = arguments.fetch("profile_agent_code", true)
72
+
73
+ if running?
74
+ msg = "Profile already in progress. Ignoring agent command to start another."
75
+ log.debug(msg)
76
+ yield(command_id, msg) if block_given?
77
+ else
78
+ start(profile_id, duration, interval, profile_agent_code)
79
+ yield(command_id) if block_given?
80
+ end
81
+ end
82
+
83
+ def stop_and_notify(command_id, arguments)
84
+ report_data = arguments.fetch("report_data", true)
85
+ stop(report_data)
86
+ yield(command_id) if block_given?
87
+ end
88
+
89
+ def log
90
+ NewRelic::Agent.logger
91
+ end
92
+ end
93
+
94
+ class ThreadProfile
95
+
96
+ attr_reader :profile_id,
97
+ :traces,
98
+ :profile_agent_code, :interval,
99
+ :poll_count, :sample_count,
100
+ :start_time, :stop_time
101
+
102
+ def initialize(profile_id, duration, interval, profile_agent_code)
103
+ @profile_id = profile_id
104
+ @profile_agent_code = profile_agent_code
105
+
106
+ @worker_loop = NewRelic::Agent::WorkerLoop.new(:duration => duration)
107
+ @interval = interval
108
+ @finished = false
109
+
110
+ @traces = {
111
+ :agent => [],
112
+ :background => [],
113
+ :other => [],
114
+ :request => []
115
+ }
116
+ @flattened_nodes = []
117
+
118
+ @poll_count = 0
119
+ @sample_count = 0
120
+ end
121
+
122
+ def run
123
+ Thread.new('Thread Profiler') do
124
+ @start_time = now_in_millis
125
+
126
+ @worker_loop.run(@interval) do
127
+ NewRelic::Agent.instance.stats_engine.
128
+ record_supportability_metrics_timed("ThreadProfiler/PollingTime") do
129
+
130
+ @poll_count += 1
131
+ Thread.list.each do |t|
132
+ @sample_count += 1
133
+
134
+ bucket = Thread.bucket_thread(t, @profile_agent_code)
135
+ backtrace = Thread.scrub_backtrace(t, @profile_agent_code)
136
+ aggregate(backtrace, @traces[bucket]) unless bucket == :ignore
137
+ end
138
+ end
139
+ end
140
+
141
+ mark_done
142
+ log.debug("Finished thread profile. Will send with next harvest.")
143
+ end
144
+ end
145
+
146
+ def stop
147
+ @worker_loop.stop
148
+ mark_done
149
+ log.debug("Stopping thread profile.")
150
+ end
151
+
152
+ def aggregate(trace, trees=@traces[:request], parent=nil)
153
+ return nil if trace.nil? || trace.empty?
154
+ node = Node.new(trace.last)
155
+ existing = trees.find {|n| n == node}
156
+
157
+ if existing.nil?
158
+ existing = node
159
+ @flattened_nodes << node
160
+ end
161
+
162
+ if parent
163
+ parent.add_child(node)
164
+ else
165
+ trees << node unless trees.include? node
166
+ end
167
+
168
+ existing.runnable_count += 1
169
+ aggregate(trace[0..-2], existing.children, existing)
170
+
171
+ existing
172
+ end
173
+
174
+ def prune!(count_to_keep)
175
+ @flattened_nodes.sort!(&:order_for_pruning)
176
+
177
+ NewRelic::Agent.instance.stats_engine.
178
+ record_supportability_metrics_count(@flattened_nodes.size, "ThreadProfiler/NodeCount")
179
+
180
+ mark_for_pruning(@flattened_nodes, count_to_keep)
181
+
182
+ traces.each { |_, nodes| Node.prune!(nodes) }
183
+ end
184
+
185
+ THREAD_PROFILER_NODES = 20_000
186
+
187
+ def to_compressed_array
188
+ prune!(THREAD_PROFILER_NODES)
189
+
190
+ traces = {
191
+ "OTHER" => @traces[:other].map{|t| t.to_array },
192
+ "REQUEST" => @traces[:request].map{|t| t.to_array },
193
+ "AGENT" => @traces[:agent].map{|t| t.to_array },
194
+ "BACKGROUND" => @traces[:background].map{|t| t.to_array }
195
+ }
196
+
197
+ [[@profile_id,
198
+ @start_time.to_f, @stop_time.to_f,
199
+ @poll_count,
200
+ ThreadProfile.compress(JSON.dump(traces)),
201
+ @sample_count, 0]]
202
+ end
203
+
204
+ def now_in_millis
205
+ Time.now.to_f * 1_000
206
+ end
207
+
208
+ def finished?
209
+ @finished
210
+ end
211
+
212
+ def mark_done
213
+ @finished = true
214
+ @stop_time = now_in_millis
215
+ end
216
+
217
+ def mark_for_pruning(nodes, count_to_keep)
218
+ to_prune = nodes[count_to_keep..-1] || []
219
+ to_prune.each { |n| n.to_prune = true }
220
+ end
221
+
222
+ def self.flattened_nodes(nodes)
223
+ nodes.map { |n| [n, flattened_nodes(n.children)] }.flatten
224
+ end
225
+
226
+ def self.compress(json)
227
+ compressed = Base64.encode64(Zlib::Deflate.deflate(json, Zlib::DEFAULT_COMPRESSION))
228
+ end
229
+
230
+ def self.parse_backtrace(trace)
231
+ trace.map do |line|
232
+ line =~ /(.*)\:(\d+)\:in `(.*)'/
233
+ { :method => $3, :line_no => $2.to_i, :file => $1 }
234
+ end
235
+ end
236
+
237
+ class Node
238
+ attr_reader :file, :method, :line_no, :children
239
+ attr_accessor :runnable_count, :to_prune, :depth
240
+
241
+ def initialize(line, parent=nil)
242
+ line =~ /(.*)\:(\d+)\:in `(.*)'/
243
+ @file = $1
244
+ @method = $3
245
+ @line_no = $2.to_i
246
+ @children = []
247
+ @runnable_count = 0
248
+ @to_prune = false
249
+ @depth = 0
250
+
251
+ parent.add_child(self) if parent
252
+ end
253
+
254
+ def ==(other)
255
+ @file == other.file &&
256
+ @method == other.method &&
257
+ @line_no == other.line_no
258
+ end
259
+
260
+ def total_count
261
+ @runnable_count
262
+ end
263
+
264
+ # Descending order on count, ascending on depth of nodes
265
+ def order_for_pruning(y)
266
+ [-runnable_count, depth] <=> [-y.runnable_count, y.depth]
267
+ end
268
+
269
+ def to_array
270
+ [[@file, @method, @line_no],
271
+ @runnable_count, 0,
272
+ @children.map {|c| c.to_array}]
273
+ end
274
+
275
+ def add_child(child)
276
+ child.depth = @depth + 1
277
+ @children << child unless @children.include? child
278
+ end
279
+
280
+ def prune!
281
+ Node.prune!(@children)
282
+ end
283
+
284
+ def self.prune!(kids)
285
+ kids.delete_if { |child| child.to_prune }
286
+ kids.each { |child| child.prune! }
287
+ end
288
+ end
289
+
290
+ def log
291
+ NewRelic::Agent.logger
292
+ end
293
+ end
294
+ end
295
+ end