wd_newrelic_rpm 3.3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (264) hide show
  1. data/CHANGELOG +591 -0
  2. data/LICENSE +64 -0
  3. data/README.rdoc +179 -0
  4. data/bin/mongrel_rpm +33 -0
  5. data/bin/newrelic +13 -0
  6. data/bin/newrelic_cmd +5 -0
  7. data/cert/cacert.pem +118 -0
  8. data/cert/oldsite.pem +28 -0
  9. data/cert/site.pem +27 -0
  10. data/install.rb +9 -0
  11. data/lib/conditional_vendored_dependency_detection.rb +3 -0
  12. data/lib/conditional_vendored_metric_parser.rb +5 -0
  13. data/lib/new_relic/agent.rb +467 -0
  14. data/lib/new_relic/agent/agent.rb +1325 -0
  15. data/lib/new_relic/agent/beacon_configuration.rb +121 -0
  16. data/lib/new_relic/agent/browser_monitoring.rb +142 -0
  17. data/lib/new_relic/agent/busy_calculator.rb +99 -0
  18. data/lib/new_relic/agent/chained_call.rb +13 -0
  19. data/lib/new_relic/agent/database.rb +223 -0
  20. data/lib/new_relic/agent/error_collector.rb +251 -0
  21. data/lib/new_relic/agent/instrumentation.rb +9 -0
  22. data/lib/new_relic/agent/instrumentation/active_merchant.rb +29 -0
  23. data/lib/new_relic/agent/instrumentation/active_record.rb +137 -0
  24. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +68 -0
  25. data/lib/new_relic/agent/instrumentation/authlogic.rb +19 -0
  26. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +443 -0
  27. data/lib/new_relic/agent/instrumentation/data_mapper.rb +238 -0
  28. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +52 -0
  29. data/lib/new_relic/agent/instrumentation/memcache.rb +80 -0
  30. data/lib/new_relic/agent/instrumentation/merb/controller.rb +41 -0
  31. data/lib/new_relic/agent/instrumentation/merb/errors.rb +29 -0
  32. data/lib/new_relic/agent/instrumentation/metric_frame.rb +353 -0
  33. data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +80 -0
  34. data/lib/new_relic/agent/instrumentation/net.rb +29 -0
  35. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +37 -0
  36. data/lib/new_relic/agent/instrumentation/queue_time.rb +210 -0
  37. data/lib/new_relic/agent/instrumentation/rack.rb +98 -0
  38. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +114 -0
  39. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +42 -0
  40. data/lib/new_relic/agent/instrumentation/rails/errors.rb +42 -0
  41. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +180 -0
  42. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +37 -0
  43. data/lib/new_relic/agent/instrumentation/sinatra.rb +78 -0
  44. data/lib/new_relic/agent/instrumentation/sunspot.rb +29 -0
  45. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +21 -0
  46. data/lib/new_relic/agent/method_tracer.rb +528 -0
  47. data/lib/new_relic/agent/sampler.rb +50 -0
  48. data/lib/new_relic/agent/samplers/cpu_sampler.rb +58 -0
  49. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +40 -0
  50. data/lib/new_relic/agent/samplers/memory_sampler.rb +143 -0
  51. data/lib/new_relic/agent/samplers/object_sampler.rb +26 -0
  52. data/lib/new_relic/agent/shim_agent.rb +29 -0
  53. data/lib/new_relic/agent/sql_sampler.rb +286 -0
  54. data/lib/new_relic/agent/stats_engine.rb +26 -0
  55. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +123 -0
  56. data/lib/new_relic/agent/stats_engine/metric_stats.rb +187 -0
  57. data/lib/new_relic/agent/stats_engine/samplers.rb +95 -0
  58. data/lib/new_relic/agent/stats_engine/transactions.rb +125 -0
  59. data/lib/new_relic/agent/transaction_info.rb +74 -0
  60. data/lib/new_relic/agent/transaction_sample_builder.rb +116 -0
  61. data/lib/new_relic/agent/transaction_sampler.rb +468 -0
  62. data/lib/new_relic/agent/worker_loop.rb +89 -0
  63. data/lib/new_relic/collection_helper.rb +77 -0
  64. data/lib/new_relic/command.rb +85 -0
  65. data/lib/new_relic/commands/deployments.rb +105 -0
  66. data/lib/new_relic/commands/install.rb +80 -0
  67. data/lib/new_relic/control.rb +46 -0
  68. data/lib/new_relic/control/class_methods.rb +53 -0
  69. data/lib/new_relic/control/configuration.rb +206 -0
  70. data/lib/new_relic/control/frameworks.rb +10 -0
  71. data/lib/new_relic/control/frameworks/external.rb +16 -0
  72. data/lib/new_relic/control/frameworks/merb.rb +31 -0
  73. data/lib/new_relic/control/frameworks/rails.rb +164 -0
  74. data/lib/new_relic/control/frameworks/rails3.rb +75 -0
  75. data/lib/new_relic/control/frameworks/ruby.rb +42 -0
  76. data/lib/new_relic/control/frameworks/sinatra.rb +20 -0
  77. data/lib/new_relic/control/instance_methods.rb +179 -0
  78. data/lib/new_relic/control/instrumentation.rb +100 -0
  79. data/lib/new_relic/control/logging_methods.rb +143 -0
  80. data/lib/new_relic/control/profiling.rb +25 -0
  81. data/lib/new_relic/control/server_methods.rb +114 -0
  82. data/lib/new_relic/data_serialization.rb +151 -0
  83. data/lib/new_relic/delayed_job_injection.rb +51 -0
  84. data/lib/new_relic/language_support.rb +73 -0
  85. data/lib/new_relic/local_environment.rb +428 -0
  86. data/lib/new_relic/merbtasks.rb +6 -0
  87. data/lib/new_relic/metric_data.rb +51 -0
  88. data/lib/new_relic/metric_spec.rb +76 -0
  89. data/lib/new_relic/metrics.rb +9 -0
  90. data/lib/new_relic/noticed_error.rb +29 -0
  91. data/lib/new_relic/rack/browser_monitoring.rb +76 -0
  92. data/lib/new_relic/rack/developer_mode.rb +268 -0
  93. data/lib/new_relic/recipes.rb +77 -0
  94. data/lib/new_relic/stats.rb +335 -0
  95. data/lib/new_relic/timer_lib.rb +27 -0
  96. data/lib/new_relic/transaction_analysis.rb +77 -0
  97. data/lib/new_relic/transaction_analysis/segment_summary.rb +49 -0
  98. data/lib/new_relic/transaction_sample.rb +261 -0
  99. data/lib/new_relic/transaction_sample/composite_segment.rb +27 -0
  100. data/lib/new_relic/transaction_sample/fake_segment.rb +9 -0
  101. data/lib/new_relic/transaction_sample/segment.rb +203 -0
  102. data/lib/new_relic/transaction_sample/summary_segment.rb +21 -0
  103. data/lib/new_relic/url_rule.rb +14 -0
  104. data/lib/new_relic/version.rb +55 -0
  105. data/lib/newrelic_rpm.rb +49 -0
  106. data/lib/tasks/all.rb +4 -0
  107. data/lib/tasks/install.rake +7 -0
  108. data/lib/tasks/tests.rake +19 -0
  109. data/newrelic.yml +265 -0
  110. data/newrelic_rpm.gemspec +312 -0
  111. data/recipes/newrelic.rb +6 -0
  112. data/test/active_record_fixtures.rb +77 -0
  113. data/test/config/newrelic.yml +48 -0
  114. data/test/config/test_control.rb +48 -0
  115. data/test/fixtures/proc_cpuinfo.txt +575 -0
  116. data/test/new_relic/agent/agent/connect_test.rb +403 -0
  117. data/test/new_relic/agent/agent/start_test.rb +255 -0
  118. data/test/new_relic/agent/agent/start_worker_thread_test.rb +153 -0
  119. data/test/new_relic/agent/agent_test.rb +140 -0
  120. data/test/new_relic/agent/agent_test_controller.rb +77 -0
  121. data/test/new_relic/agent/agent_test_controller_test.rb +382 -0
  122. data/test/new_relic/agent/apdex_from_server_test.rb +9 -0
  123. data/test/new_relic/agent/beacon_configuration_test.rb +111 -0
  124. data/test/new_relic/agent/browser_monitoring_test.rb +323 -0
  125. data/test/new_relic/agent/busy_calculator_test.rb +81 -0
  126. data/test/new_relic/agent/database_test.rb +149 -0
  127. data/test/new_relic/agent/error_collector/notice_error_test.rb +257 -0
  128. data/test/new_relic/agent/error_collector_test.rb +192 -0
  129. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +576 -0
  130. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +34 -0
  131. data/test/new_relic/agent/instrumentation/instrumentation_test.rb +11 -0
  132. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +171 -0
  133. data/test/new_relic/agent/instrumentation/metric_frame_test.rb +50 -0
  134. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +84 -0
  135. data/test/new_relic/agent/instrumentation/queue_time_test.rb +382 -0
  136. data/test/new_relic/agent/instrumentation/rack_test.rb +35 -0
  137. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +184 -0
  138. data/test/new_relic/agent/memcache_instrumentation_test.rb +143 -0
  139. data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +164 -0
  140. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +234 -0
  141. data/test/new_relic/agent/method_tracer_test.rb +386 -0
  142. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  143. data/test/new_relic/agent/rpm_agent_test.rb +149 -0
  144. data/test/new_relic/agent/sampler_test.rb +19 -0
  145. data/test/new_relic/agent/shim_agent_test.rb +20 -0
  146. data/test/new_relic/agent/sql_sampler_test.rb +192 -0
  147. data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +150 -0
  148. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +82 -0
  149. data/test/new_relic/agent/stats_engine/samplers_test.rb +99 -0
  150. data/test/new_relic/agent/stats_engine_test.rb +220 -0
  151. data/test/new_relic/agent/transaction_info_test.rb +13 -0
  152. data/test/new_relic/agent/transaction_sample_builder_test.rb +219 -0
  153. data/test/new_relic/agent/transaction_sampler_test.rb +967 -0
  154. data/test/new_relic/agent/worker_loop_test.rb +66 -0
  155. data/test/new_relic/agent_test.rb +187 -0
  156. data/test/new_relic/collection_helper_test.rb +149 -0
  157. data/test/new_relic/command/deployments_test.rb +68 -0
  158. data/test/new_relic/control/class_methods_test.rb +62 -0
  159. data/test/new_relic/control/configuration_test.rb +84 -0
  160. data/test/new_relic/control/logging_methods_test.rb +185 -0
  161. data/test/new_relic/control_test.rb +256 -0
  162. data/test/new_relic/data_serialization_test.rb +208 -0
  163. data/test/new_relic/delayed_job_injection_test.rb +16 -0
  164. data/test/new_relic/local_environment_test.rb +85 -0
  165. data/test/new_relic/metric_data_test.rb +125 -0
  166. data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
  167. data/test/new_relic/metric_spec_test.rb +95 -0
  168. data/test/new_relic/rack/all_test.rb +11 -0
  169. data/test/new_relic/rack/browser_monitoring_test.rb +142 -0
  170. data/test/new_relic/rack/developer_mode_helper_test.rb +141 -0
  171. data/test/new_relic/rack/developer_mode_test.rb +74 -0
  172. data/test/new_relic/stats_test.rb +411 -0
  173. data/test/new_relic/transaction_analysis/segment_summary_test.rb +91 -0
  174. data/test/new_relic/transaction_analysis_test.rb +121 -0
  175. data/test/new_relic/transaction_sample/composite_segment_test.rb +35 -0
  176. data/test/new_relic/transaction_sample/fake_segment_test.rb +17 -0
  177. data/test/new_relic/transaction_sample/segment_test.rb +389 -0
  178. data/test/new_relic/transaction_sample/summary_segment_test.rb +31 -0
  179. data/test/new_relic/transaction_sample_subtest_test.rb +56 -0
  180. data/test/new_relic/transaction_sample_test.rb +177 -0
  181. data/test/new_relic/version_number_test.rb +89 -0
  182. data/test/script/build_test_gem.sh +51 -0
  183. data/test/script/ci.sh +94 -0
  184. data/test/script/ci_bench.sh +52 -0
  185. data/test/test_contexts.rb +29 -0
  186. data/test/test_helper.rb +155 -0
  187. data/ui/helpers/developer_mode_helper.rb +357 -0
  188. data/ui/helpers/google_pie_chart.rb +48 -0
  189. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  190. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  191. data/ui/views/newrelic/_sample.rhtml +20 -0
  192. data/ui/views/newrelic/_segment.rhtml +28 -0
  193. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  194. data/ui/views/newrelic/_segment_row.rhtml +12 -0
  195. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  196. data/ui/views/newrelic/_show_sample_sql.rhtml +24 -0
  197. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  198. data/ui/views/newrelic/_sql_row.rhtml +16 -0
  199. data/ui/views/newrelic/_stack_trace.rhtml +15 -0
  200. data/ui/views/newrelic/_table.rhtml +12 -0
  201. data/ui/views/newrelic/explain_sql.rhtml +43 -0
  202. data/ui/views/newrelic/file/images/arrow-close.png +0 -0
  203. data/ui/views/newrelic/file/images/arrow-open.png +0 -0
  204. data/ui/views/newrelic/file/images/blue_bar.gif +0 -0
  205. data/ui/views/newrelic/file/images/file_icon.png +0 -0
  206. data/ui/views/newrelic/file/images/gray_bar.gif +0 -0
  207. data/ui/views/newrelic/file/images/new-relic-rpm-desktop.gif +0 -0
  208. data/ui/views/newrelic/file/images/new_relic_rpm_desktop.gif +0 -0
  209. data/ui/views/newrelic/file/images/textmate.png +0 -0
  210. data/ui/views/newrelic/file/javascript/jquery-1.4.2.js +6240 -0
  211. data/ui/views/newrelic/file/javascript/transaction_sample.js +120 -0
  212. data/ui/views/newrelic/file/stylesheets/style.css +490 -0
  213. data/ui/views/newrelic/index.rhtml +71 -0
  214. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  215. data/ui/views/newrelic/show_sample.rhtml +80 -0
  216. data/ui/views/newrelic/show_source.rhtml +3 -0
  217. data/ui/views/newrelic/threads.rhtml +53 -0
  218. data/vendor/gems/dependency_detection-0.0.1.build/LICENSE +5 -0
  219. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +67 -0
  220. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection/version.rb +3 -0
  221. data/vendor/gems/metric_parser-0.1.0.pre1/lib/metric_parser.rb +1 -0
  222. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser.rb +64 -0
  223. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/action_mailer.rb +14 -0
  224. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/active_merchant.rb +31 -0
  225. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/active_record.rb +33 -0
  226. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/apdex.rb +89 -0
  227. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/background_transaction.rb +7 -0
  228. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/client.rb +46 -0
  229. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller.rb +67 -0
  230. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller_cpu.rb +43 -0
  231. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller_ext.rb +17 -0
  232. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/database.rb +48 -0
  233. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/database_pool.rb +24 -0
  234. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/dot_net.rb +28 -0
  235. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/dot_net_parser.rb +17 -0
  236. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/errors.rb +11 -0
  237. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/external.rb +55 -0
  238. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/frontend.rb +40 -0
  239. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/gc.rb +20 -0
  240. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/hibernate_session.rb +7 -0
  241. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/java.rb +31 -0
  242. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/java_parser.rb +17 -0
  243. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/jsp.rb +34 -0
  244. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/jsp_tag.rb +7 -0
  245. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/mem_cache.rb +55 -0
  246. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +135 -0
  247. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/orm.rb +27 -0
  248. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/other_transaction.rb +40 -0
  249. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet.rb +7 -0
  250. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet_context_listener.rb +7 -0
  251. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet_filter.rb +7 -0
  252. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr.rb +27 -0
  253. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr_request_handler.rb +15 -0
  254. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring.rb +54 -0
  255. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring_controller.rb +6 -0
  256. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring_view.rb +6 -0
  257. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/struts_action.rb +20 -0
  258. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/struts_result.rb +20 -0
  259. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/version.rb +5 -0
  260. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/view.rb +70 -0
  261. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_frontend.rb +18 -0
  262. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_service.rb +14 -0
  263. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_transaction.rb +133 -0
  264. metadata +376 -0
@@ -0,0 +1,1325 @@
1
+ require 'socket'
2
+ require 'net/https'
3
+ require 'net/http'
4
+ require 'logger'
5
+ require 'zlib'
6
+ require 'stringio'
7
+ require 'new_relic/data_serialization'
8
+
9
+ module NewRelic
10
+ module Agent
11
+
12
+ # The Agent is a singleton that is instantiated when the plugin is
13
+ # activated. It collects performance data from ruby applications
14
+ # in realtime as the application runs, and periodically sends that
15
+ # data to the NewRelic server.
16
+ class Agent
17
+
18
+ # Specifies the version of the agent's communication protocol with
19
+ # the NewRelic hosted site.
20
+
21
+ PROTOCOL_VERSION = 8
22
+ # 14105: v8 (tag 2.10.3)
23
+ # (no v7)
24
+ # 10379: v6 (not tagged)
25
+ # 4078: v5 (tag 2.5.4)
26
+ # 2292: v4 (tag 2.3.6)
27
+ # 1754: v3 (tag 2.3.0)
28
+ # 534: v2 (shows up in 2.1.0, our first tag)
29
+
30
+
31
+ def initialize
32
+
33
+ @launch_time = Time.now
34
+
35
+ @metric_ids = {}
36
+ @stats_engine = NewRelic::Agent::StatsEngine.new
37
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new
38
+ @sql_sampler = NewRelic::Agent::SqlSampler.new
39
+ @stats_engine.transaction_sampler = @transaction_sampler
40
+ @error_collector = NewRelic::Agent::ErrorCollector.new
41
+ @connect_attempts = 0
42
+
43
+ @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
44
+
45
+ @last_harvest_time = Time.now
46
+ @obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
47
+ end
48
+
49
+ # contains all the class-level methods for NewRelic::Agent::Agent
50
+ module ClassMethods
51
+ # Should only be called by NewRelic::Control - returns a
52
+ # memoized singleton instance of the agent, creating one if needed
53
+ def instance
54
+ @instance ||= self.new
55
+ end
56
+ end
57
+
58
+ # Holds all the methods defined on NewRelic::Agent::Agent
59
+ # instances
60
+ module InstanceMethods
61
+
62
+ # holds a proc that is used to obfuscate sql statements
63
+ attr_reader :obfuscator
64
+ # the statistics engine that holds all the timeslice data
65
+ attr_reader :stats_engine
66
+ # the transaction sampler that handles recording transactions
67
+ attr_reader :transaction_sampler
68
+ attr_reader :sql_sampler
69
+ # error collector is a simple collection of recorded errors
70
+ attr_reader :error_collector
71
+ # whether we should record raw, obfuscated, or no sql
72
+ attr_reader :record_sql
73
+ # a cached set of metric_ids to save the collector some time -
74
+ # it returns a metric id for every metric name we send it, and
75
+ # in the future we transmit using the metric id only
76
+ attr_reader :metric_ids
77
+ # in theory a set of rules applied by the agent to the output
78
+ # of its metrics. Currently unimplemented
79
+ attr_reader :url_rules
80
+ # a configuration for the Real User Monitoring system -
81
+ # handles things like static setup of the header for inclusion
82
+ # into pages
83
+ attr_reader :beacon_configuration
84
+
85
+ # Returns the length of the unsent errors array, if it exists,
86
+ # otherwise nil
87
+ def unsent_errors_size
88
+ @unsent_errors.length if @unsent_errors
89
+ end
90
+
91
+ # Returns the length of the traces array, if it exists,
92
+ # otherwise nil
93
+ def unsent_traces_size
94
+ @traces.length if @traces
95
+ end
96
+
97
+ # Initializes the unsent timeslice data hash, if needed, and
98
+ # returns the number of keys it contains
99
+ def unsent_timeslice_data
100
+ @unsent_timeslice_data ||= {}
101
+ @unsent_timeslice_data.keys.length
102
+ end
103
+
104
+ # fakes out a transaction that did not happen in this process
105
+ # by creating apdex, summary metrics, and recording statistics
106
+ # for the transaction
107
+ #
108
+ # This method is *deprecated* - it may be removed in future
109
+ # versions of the agent
110
+ def record_transaction(duration_seconds, options={})
111
+ is_error = options['is_error'] || options['error_message'] || options['exception']
112
+ metric = options['metric']
113
+ metric ||= options['uri'] # normalize this with url rules
114
+ raise "metric or uri arguments required" unless metric
115
+ metric_info = NewRelic::MetricParser::MetricParser.for_metric_named(metric)
116
+
117
+ if metric_info.is_web_transaction?
118
+ NewRelic::Agent::Instrumentation::MetricFrame.record_apdex(metric_info, duration_seconds, duration_seconds, is_error)
119
+ end
120
+ metrics = metric_info.summary_metrics
121
+
122
+ metrics << metric
123
+ metrics.each do |name|
124
+ stats = stats_engine.get_stats_no_scope(name)
125
+ stats.record_data_point(duration_seconds)
126
+ end
127
+
128
+ if is_error
129
+ if options['exception']
130
+ e = options['exception']
131
+ elsif options['error_message']
132
+ e = StandardError.new options['error_message']
133
+ else
134
+ e = StandardError.new 'Unknown Error'
135
+ end
136
+ error_collector.notice_error e, :uri => options['uri'], :metric => metric
137
+ end
138
+ # busy time ?
139
+ end
140
+
141
+ # This method should be called in a forked process after a fork.
142
+ # It assumes the parent process initialized the agent, but does
143
+ # not assume the agent started.
144
+ #
145
+ # The call is idempotent, but not re-entrant.
146
+ #
147
+ # * It clears any metrics carried over from the parent process
148
+ # * Restarts the sampler thread if necessary
149
+ # * Initiates a new agent run and worker loop unless that was done
150
+ # in the parent process and +:force_reconnect+ is not true
151
+ #
152
+ # Options:
153
+ # * <tt>:force_reconnect => true</tt> to force the spawned process to
154
+ # establish a new connection, such as when forking a long running process.
155
+ # The default is false--it will only connect to the server if the parent
156
+ # had not connected.
157
+ # * <tt>:keep_retrying => false</tt> if we try to initiate a new
158
+ # connection, this tells me to only try it once so this method returns
159
+ # quickly if there is some kind of latency with the server.
160
+ def after_fork(options={})
161
+
162
+ # @connected gets false after we fail to connect or have an error
163
+ # connecting. @connected has nil if we haven't finished trying to connect.
164
+ # or we didn't attempt a connection because this is the master process
165
+
166
+ # log.debug "Agent received after_fork notice in #$$: [#{control.agent_enabled?}; monitor=#{control.monitor_mode?}; connected: #{@connected.inspect}; thread=#{@worker_thread.inspect}]"
167
+ return if !control.agent_enabled? or
168
+ !control.monitor_mode? or
169
+ @connected == false or
170
+ @worker_thread && @worker_thread.alive?
171
+
172
+ log.info "Starting the worker thread in #$$ after forking."
173
+
174
+ # Clear out stats that are left over from parent process
175
+ reset_stats
176
+
177
+ # Don't ever check to see if this is a spawner. If we're in a forked process
178
+ # I'm pretty sure we're not also forking new instances.
179
+ start_worker_thread(options)
180
+ @stats_engine.start_sampler_thread
181
+ end
182
+
183
+ # True if we have initialized and completed 'start'
184
+ def started?
185
+ @started
186
+ end
187
+
188
+ # Return nil if not yet connected, true if successfully started
189
+ # and false if we failed to start.
190
+ def connected?
191
+ @connected
192
+ end
193
+
194
+ # Attempt a graceful shutdown of the agent, running the worker
195
+ # loop if it exists and is running.
196
+ #
197
+ # Options:
198
+ # :force_send => (true/false) # force the agent to send data
199
+ # before shutting down
200
+ def shutdown(options={})
201
+ run_loop_before_exit = options.fetch(:force_send, false)
202
+ return if not started?
203
+ if @worker_loop
204
+ @worker_loop.run_task if run_loop_before_exit
205
+ @worker_loop.stop
206
+
207
+ log.debug "Starting Agent shutdown"
208
+
209
+ # if litespeed, then ignore all future SIGUSR1 - it's
210
+ # litespeed trying to shut us down
211
+
212
+ if control.dispatcher == :litespeed
213
+ Signal.trap("SIGUSR1", "IGNORE")
214
+ Signal.trap("SIGTERM", "IGNORE")
215
+ end
216
+
217
+ begin
218
+ NewRelic::Agent.disable_all_tracing do
219
+ graceful_disconnect
220
+ end
221
+ rescue => e
222
+ log.error e
223
+ log.error e.backtrace.join("\n")
224
+ end
225
+ end
226
+ @started = nil
227
+ end
228
+
229
+ # Tells the statistics engine we are starting a new transaction
230
+ def start_transaction
231
+ @stats_engine.start_transaction
232
+ end
233
+
234
+ # Tells the statistics engine we are ending a transaction
235
+ def end_transaction
236
+ @stats_engine.end_transaction
237
+ end
238
+
239
+ # Sets a thread local variable as to whether we should or
240
+ # should not record sql in the current thread. Returns the
241
+ # previous value, if there is one
242
+ def set_record_sql(should_record)
243
+ prev = Thread::current[:record_sql]
244
+ Thread::current[:record_sql] = should_record
245
+ prev.nil? || prev
246
+ end
247
+
248
+ # Sets a thread local variable as to whether we should or
249
+ # should not record transaction traces in the current
250
+ # thread. Returns the previous value, if there is one
251
+ def set_record_tt(should_record)
252
+ prev = Thread::current[:record_tt]
253
+ Thread::current[:record_tt] = should_record
254
+ prev.nil? || prev
255
+ end
256
+
257
+ # Push flag indicating whether we should be tracing in this
258
+ # thread. This uses a stack which allows us to disable tracing
259
+ # children of a transaction without affecting the tracing of
260
+ # the whole transaction
261
+ def push_trace_execution_flag(should_trace=false)
262
+ value = Thread.current[:newrelic_untraced]
263
+ if (value.nil?)
264
+ Thread.current[:newrelic_untraced] = []
265
+ end
266
+
267
+ Thread.current[:newrelic_untraced] << should_trace
268
+ end
269
+
270
+ # Pop the current trace execution status. Restore trace execution status
271
+ # to what it was before we pushed the current flag.
272
+ def pop_trace_execution_flag
273
+ Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
274
+ end
275
+
276
+ # Shorthand to the NewRelic::Agent.logger method
277
+ def log
278
+ NewRelic::Agent.logger
279
+ end
280
+
281
+ # Herein lies the corpse of the former 'start' method. May
282
+ # it's unmatched flog score rest in pieces.
283
+ module Start
284
+ # Check whether we have already started, which is an error condition
285
+ def already_started?
286
+ if started?
287
+ control.log!("Agent Started Already!", :error)
288
+ true
289
+ end
290
+ end
291
+
292
+ # The agent is disabled when it is not force enabled by the
293
+ # 'agent_enabled' option (e.g. in a manual start), or
294
+ # enabled normally through the configuration file
295
+ def disabled?
296
+ !control.agent_enabled?
297
+ end
298
+
299
+ # Logs the dispatcher to the log file to assist with
300
+ # debugging. When no debugger is present, logs this fact to
301
+ # assist with proper dispatcher detection
302
+ def log_dispatcher
303
+ dispatcher_name = control.dispatcher.to_s
304
+ return if log_if(dispatcher_name.empty?, :info, "No dispatcher detected.")
305
+ log.info "Dispatcher: #{dispatcher_name}"
306
+ end
307
+
308
+ # Logs the configured application names
309
+ def log_app_names
310
+ log.info "Application: #{control.app_names.join(", ")}"
311
+ end
312
+
313
+ # Connecting in the foreground blocks further startup of the
314
+ # agent until we have a connection - useful in cases where
315
+ # you're trying to log a very-short-running process and want
316
+ # to get statistics from before a server connection
317
+ # (typically 20 seconds) exists
318
+ def connect_in_foreground
319
+ NewRelic::Agent.disable_all_tracing { connect(:keep_retrying => false) }
320
+ end
321
+
322
+ # If we're using sinatra, old versions run in an at_exit
323
+ # block so we should probably know that
324
+ def using_sinatra?
325
+ defined?(Sinatra::Application)
326
+ end
327
+
328
+ # we should not set an at_exit block if people are using
329
+ # these as they don't do standard at_exit behavior per MRI/YARV
330
+ def weird_ruby?
331
+ NewRelic::LanguageSupport.using_engine?('rbx') ||
332
+ NewRelic::LanguageSupport.using_engine?('jruby') ||
333
+ using_sinatra?
334
+ end
335
+
336
+ # Installs our exit handler, which exploits the weird
337
+ # behavior of at_exit blocks to make sure it runs last, by
338
+ # doing an at_exit within an at_exit block.
339
+ def install_exit_handler
340
+ if control.send_data_on_exit && !weird_ruby?
341
+ # Our shutdown handler needs to run after other shutdown handlers
342
+ at_exit { at_exit { shutdown } }
343
+ end
344
+ end
345
+
346
+ # Tells us in the log file where the log file is
347
+ # located. This seems redundant, but can come in handy when
348
+ # we have some log file path set by the user which parses
349
+ # incorrectly, sending the log file to who-knows-where
350
+ def notify_log_file_location
351
+ log_file = NewRelic::Control.instance.log_file
352
+ log_if(File.exists?(log_file.to_s), :info,
353
+ "Agent Log at #{log_file}")
354
+ end
355
+
356
+ # Classy logging of the agent version and the current pid,
357
+ # so we can disambiguate processes in the log file and make
358
+ # sure they're running a reasonable version
359
+ def log_version_and_pid
360
+ log.info "New Relic Ruby Agent #{NewRelic::VERSION::STRING} Initialized: pid = #{$$}"
361
+ end
362
+
363
+ # A helper method that logs a condition if that condition is
364
+ # true. Mentally cleaner than having every method set a
365
+ # local and log if it is true
366
+ def log_if(boolean, level, message)
367
+ self.log.send(level, message) if boolean
368
+ boolean
369
+ end
370
+
371
+ # A helper method that logs a condition unless that
372
+ # condition is true. Mentally cleaner than having every
373
+ # method set a local and log unless it is true
374
+ def log_unless(boolean, level, message)
375
+ self.log.send(level, message) unless boolean
376
+ boolean
377
+ end
378
+
379
+ # Warn the user if they have configured their agent not to
380
+ # send data, that way we can see this clearly in the log file
381
+ def monitoring?
382
+ log_unless(control.monitor_mode?, :warn, "Agent configured not to send data in this environment - edit newrelic.yml to change this")
383
+ end
384
+
385
+ # Tell the user when the license key is missing so they can
386
+ # fix it by adding it to the file
387
+ def has_license_key?
388
+ log_unless(control.license_key, :error, "No license key found. Please edit your newrelic.yml file and insert your license key.")
389
+ end
390
+
391
+ # A correct license key exists and is of the proper length
392
+ def has_correct_license_key?
393
+ has_license_key? && correct_license_length
394
+ end
395
+
396
+ # A license key is an arbitrary 40 character string,
397
+ # usually looks something like a SHA1 hash
398
+ def correct_license_length
399
+ key = control.license_key
400
+ log_unless((key.length == 40), :error, "Invalid license key: #{key}")
401
+ end
402
+
403
+ # If we're using a dispatcher that forks before serving
404
+ # requests, we need to wait until the children are forked
405
+ # before connecting, otherwise the parent process sends odd data
406
+ def using_forking_dispatcher?
407
+ log_if([:passenger, :unicorn].include?(control.dispatcher), :info, "Connecting workers after forking.")
408
+ end
409
+
410
+ # Sanity-check the agent configuration and start the agent,
411
+ # setting up the worker thread and the exit handler to shut
412
+ # down the agent
413
+ def check_config_and_start_agent
414
+ return unless monitoring? && has_correct_license_key?
415
+ return if using_forking_dispatcher?
416
+ connect_in_foreground if control.sync_startup
417
+ start_worker_thread
418
+ install_exit_handler
419
+ end
420
+ end
421
+
422
+ include Start
423
+
424
+ # Logs a bunch of data and starts the agent, if needed
425
+ def start
426
+ return if already_started? || disabled?
427
+ @started = true
428
+ @local_host = determine_host
429
+ log_dispatcher
430
+ log_app_names
431
+ config_transaction_tracer
432
+ check_config_and_start_agent
433
+ log_version_and_pid
434
+ notify_log_file_location
435
+ end
436
+
437
+ # Clear out the metric data, errors, and transaction traces,
438
+ # making sure the agent is in a fresh state
439
+ def reset_stats
440
+ @stats_engine.reset_stats
441
+ @unsent_errors = []
442
+ @traces = nil
443
+ @unsent_timeslice_data = {}
444
+ @last_harvest_time = Time.now
445
+ @launch_time = Time.now
446
+ end
447
+
448
+ private
449
+ def collector
450
+ @collector ||= control.server
451
+ end
452
+
453
+ # All of this module used to be contained in the
454
+ # start_worker_thread method - this is an artifact of
455
+ # refactoring and can be moved, renamed, etc at will
456
+ module StartWorkerThread
457
+
458
+ # disable transaction sampling if disabled by the server
459
+ # and we're not in dev mode
460
+ def check_transaction_sampler_status
461
+ if control.developer_mode? || @should_send_samples
462
+ @transaction_sampler.enable
463
+ else
464
+ @transaction_sampler.disable
465
+ end
466
+ end
467
+
468
+ def check_sql_sampler_status
469
+ # disable sql sampling if disabled by the server
470
+ # and we're not in dev mode
471
+ if @sql_sampler.config.fetch('enabled', true) && ['raw', 'obfuscated'].include?(@sql_sampler.config.fetch('record_sql', 'obfuscated').to_s) && @transaction_sampler.config.fetch('enabled', true)
472
+ @sql_sampler.enable
473
+ else
474
+ @sql_sampler.disable
475
+ end
476
+ end
477
+
478
+ # logs info about the worker loop so users can see when the
479
+ # agent actually begins running in the background
480
+ def log_worker_loop_start
481
+ log.info "Reporting performance data every #{@report_period} seconds."
482
+ log.debug "Running worker loop"
483
+ end
484
+
485
+ # Creates the worker loop and loads it with the instructions
486
+ # it should run every @report_period seconds
487
+ def create_and_run_worker_loop
488
+ @worker_loop = WorkerLoop.new
489
+ @worker_loop.run(@report_period) do
490
+ save_or_transmit_data
491
+ end
492
+ end
493
+
494
+ # Handles the case where the server tells us to restart -
495
+ # this clears the data, clears connection attempts, and
496
+ # waits a while to reconnect.
497
+ def handle_force_restart(error)
498
+ log.info error.message
499
+ reset_stats
500
+ @metric_ids = {}
501
+ @connected = nil
502
+ sleep 30
503
+ end
504
+
505
+ # when a disconnect is requested, stop the current thread, which
506
+ # is the worker thread that gathers data and talks to the
507
+ # server.
508
+ def handle_force_disconnect(error)
509
+ log.error "New Relic forced this agent to disconnect (#{error.message})"
510
+ disconnect
511
+ end
512
+
513
+ # there is a problem with connecting to the server, so we
514
+ # stop trying to connect and shut down the agent
515
+ def handle_server_connection_problem(error)
516
+ log.error "Unable to establish connection with the server. Run with log level set to debug for more information."
517
+ log.debug("#{error.class.name}: #{error.message}\n#{error.backtrace.first}")
518
+ disconnect
519
+ end
520
+
521
+ # Handles an unknown error in the worker thread by logging
522
+ # it and disconnecting the agent, since we are now in an
523
+ # unknown state
524
+ def handle_other_error(error)
525
+ log.error "Terminating worker loop: #{error.class.name}: #{error.message}\n #{error.backtrace.join("\n ")}"
526
+ disconnect
527
+ end
528
+
529
+ # a wrapper method to handle all the errors that can happen
530
+ # in the connection and worker thread system. This
531
+ # guarantees a no-throw from the background thread.
532
+ def catch_errors
533
+ yield
534
+ rescue NewRelic::Agent::ForceRestartException => e
535
+ handle_force_restart(e)
536
+ retry
537
+ rescue NewRelic::Agent::ForceDisconnectException => e
538
+ handle_force_disconnect(e)
539
+ rescue NewRelic::Agent::ServerConnectionException => e
540
+ handle_server_connection_problem(e)
541
+ rescue => e
542
+ handle_other_error(e)
543
+ end
544
+
545
+ # This is the method that is run in a new thread in order to
546
+ # background the harvesting and sending of data during the
547
+ # normal operation of the agent.
548
+ #
549
+ # Takes connection options that determine how we should
550
+ # connect to the server, and loops endlessly - typically we
551
+ # never return from this method unless we're shutting down
552
+ # the agent
553
+ def deferred_work!(connection_options)
554
+ catch_errors do
555
+ NewRelic::Agent.disable_all_tracing do
556
+ # We try to connect. If this returns false that means
557
+ # the server rejected us for a licensing reason and we should
558
+ # just exit the thread. If it returns nil
559
+ # that means it didn't try to connect because we're in the master.
560
+ connect(connection_options)
561
+ if @connected
562
+ check_transaction_sampler_status
563
+ check_sql_sampler_status
564
+ log_worker_loop_start
565
+ create_and_run_worker_loop
566
+ # never reaches here unless there is a problem or
567
+ # the agent is exiting
568
+ else
569
+ log.debug "No connection. Worker thread ending."
570
+ end
571
+ end
572
+ end
573
+ end
574
+ end
575
+ include StartWorkerThread
576
+
577
+ # Try to launch the worker thread and connect to the server.
578
+ #
579
+ # See #connect for a description of connection_options.
580
+ def start_worker_thread(connection_options = {})
581
+ log.debug "Creating Ruby Agent worker thread."
582
+ @worker_thread = Thread.new do
583
+ deferred_work!(connection_options)
584
+ end # thread new
585
+ @worker_thread['newrelic_label'] = 'Worker Loop'
586
+ end
587
+
588
+ # A shorthand for NewRelic::Control.instance
589
+ def control
590
+ NewRelic::Control.instance
591
+ end
592
+
593
+ # This module is an artifact of a refactoring of the connect
594
+ # method - all of its methods are used in that context, so it
595
+ # can be refactored at will. It should be fully tested
596
+ module Connect
597
+ # the frequency with which we should try to connect to the
598
+ # server at the moment.
599
+ attr_accessor :connect_retry_period
600
+ # number of attempts we've made to contact the server
601
+ attr_accessor :connect_attempts
602
+
603
+ # Disconnect just sets connected to false, which prevents
604
+ # the agent from trying to connect again
605
+ def disconnect
606
+ @connected = false
607
+ true
608
+ end
609
+
610
+ # We've tried to connect if @connected is not nil, or if we
611
+ # are forcing reconnection (i.e. in the case of an
612
+ # after_fork with long running processes)
613
+ def tried_to_connect?(options)
614
+ !(@connected.nil? || options[:force_reconnect])
615
+ end
616
+
617
+ # We keep trying by default, but you can disable it with the
618
+ # :keep_retrying option set to false
619
+ def should_keep_retrying?(options)
620
+ @keep_retrying = (options[:keep_retrying].nil? || options[:keep_retrying])
621
+ end
622
+
623
+ # Retry period is a minute for each failed attempt that
624
+ # we've made. This should probably do some sort of sane TCP
625
+ # backoff to prevent hammering the server, but a minute for
626
+ # each attempt seems to work reasonably well.
627
+ def get_retry_period
628
+ return 600 if self.connect_attempts > 6
629
+ connect_attempts * 60
630
+ end
631
+
632
+ def increment_retry_period! #:nodoc:
633
+ self.connect_retry_period=(get_retry_period)
634
+ end
635
+
636
+ # We should only retry when there has not been a more
637
+ # serious condition that would prevent it. We increment the
638
+ # connect attempts and the retry period, to prevent constant
639
+ # connection attempts, and tell the user what we're doing by
640
+ # logging.
641
+ def should_retry?
642
+ if @keep_retrying
643
+ self.connect_attempts=(connect_attempts + 1)
644
+ increment_retry_period!
645
+ log.info "Will re-attempt in #{connect_retry_period} seconds"
646
+ true
647
+ else
648
+ disconnect
649
+ false
650
+ end
651
+ end
652
+
653
+ # When we have a problem connecting to the server, we need
654
+ # to tell the user what happened, since this is not an error
655
+ # we can handle gracefully.
656
+ def log_error(error)
657
+ log.error "Error establishing connection with New Relic Service at #{control.server}: #{error.message}"
658
+ log.debug error.backtrace.join("\n")
659
+ end
660
+
661
+ # When the server sends us an error with the license key, we
662
+ # want to tell the user that something went wrong, and let
663
+ # them know where to go to get a valid license key
664
+ #
665
+ # After this runs, it disconnects the agent so that it will
666
+ # no longer try to connect to the server, saving the
667
+ # application and the server load
668
+ def handle_license_error(error)
669
+ log.error error.message
670
+ log.info "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
671
+ disconnect
672
+ end
673
+
674
+ # If we are using a seed and token to validate the agent, we
675
+ # should debug log that fact so that debug logs include a
676
+ # clue that token authentication is what will be used
677
+ def log_seed_token
678
+ if control.validate_seed
679
+ log.debug "Connecting with validation seed/token: #{control.validate_seed}/#{control.validate_token}"
680
+ end
681
+ end
682
+
683
+ # Checks whether we should send environment info, and if so,
684
+ # returns the snapshot from the local environment
685
+ def environment_for_connect
686
+ control['send_environment_info'] != false ? control.local_env.snapshot : []
687
+ end
688
+
689
+ # These validation settings are used for cases where a
690
+ # dynamic server is spun up for clients - partners can
691
+ # include a seed and token to indicate that the host is
692
+ # allowed to connect, rather than setting a unique hostname
693
+ def validate_settings
694
+ {
695
+ :seed => control.validate_seed,
696
+ :token => control.validate_token
697
+ }
698
+ end
699
+
700
+ # Initializes the hash of settings that we send to the
701
+ # server. Returns a literal hash containing the options
702
+ def connect_settings
703
+ {
704
+ :pid => $$,
705
+ :host => @local_host,
706
+ :app_name => control.app_names,
707
+ :language => 'ruby',
708
+ :agent_version => NewRelic::VERSION::STRING,
709
+ :environment => environment_for_connect,
710
+ :settings => control.settings,
711
+ :validate => validate_settings
712
+ }
713
+ end
714
+
715
+ # Does some simple logging to make sure that our seed and
716
+ # token for verification are correct, then returns the
717
+ # connect data passed back from the server
718
+ def connect_to_server
719
+ log_seed_token
720
+ connect_data = invoke_remote(:connect, connect_settings)
721
+ end
722
+
723
+ # Configures the error collector if the server says that we
724
+ # are allowed to send errors. Pretty simple, and logs at
725
+ # debug whether errors will or will not be sent.
726
+ def configure_error_collector!(server_enabled)
727
+ # Reinitialize the error collector
728
+ @error_collector = NewRelic::Agent::ErrorCollector.new
729
+ # Ask for permission to collect error data
730
+ enabled = if error_collector.config_enabled && server_enabled
731
+ error_collector.enabled = true
732
+ else
733
+ error_collector.enabled = false
734
+ end
735
+ log.debug "Errors will #{enabled ? '' : 'not '}be sent to the New Relic service."
736
+ end
737
+
738
+ # Random sampling is enabled based on a sample rate, which
739
+ # is the n in "every 1/n transactions is added regardless of
740
+ # its length".
741
+ #
742
+ # uses a sane default for sampling rate if the sampling rate
743
+ # is zero, since the collector currently sends '0' as a
744
+ # sampling rate for all accounts, which is probably for
745
+ # legacy reasons
746
+ def enable_random_samples!(sample_rate)
747
+ sample_rate = 10 unless sample_rate.to_i > 0
748
+ @transaction_sampler.random_sampling = true
749
+ @transaction_sampler.sampling_rate = sample_rate
750
+ log.info "Transaction sampling enabled, rate = #{@transaction_sampler.sampling_rate}"
751
+ end
752
+
753
+ # this entire method should be done on the transaction
754
+ # sampler object, rather than here. We should pass in the
755
+ # sampler config.
756
+ def config_transaction_tracer
757
+ # Reconfigure the transaction tracer
758
+ @transaction_sampler.configure!
759
+ @sql_sampler.configure!
760
+ @should_send_samples = @config_should_send_samples = @transaction_sampler.config.fetch('enabled', true)
761
+ @should_send_random_samples = @transaction_sampler.config.fetch('random_sample', false)
762
+ set_sql_recording!
763
+
764
+ # default to 2.0, string 'apdex_f' will turn into your
765
+ # apdex * 4
766
+ @slowest_transaction_threshold = @transaction_sampler.config.fetch('transaction_threshold', 2.0).to_f
767
+ @slowest_transaction_threshold = apdex_f if apdex_f_threshold?
768
+ end
769
+
770
+ # Enables or disables the transaction tracer and sets its
771
+ # options based on the options provided to the
772
+ # method.
773
+ def configure_transaction_tracer!(server_enabled, sample_rate)
774
+ # Ask the server for permission to send transaction samples.
775
+ # determined by subscription license.
776
+ @transaction_sampler.config['enabled'] = server_enabled
777
+ @sql_sampler.configure!
778
+ @should_send_samples = @config_should_send_samples && server_enabled
779
+
780
+ if @should_send_samples
781
+ # I don't think this is ever true, but...
782
+ enable_random_samples!(sample_rate) if @should_send_random_samples
783
+
784
+ @transaction_sampler.slow_capture_threshold = @slowest_transaction_threshold
785
+
786
+ log.debug "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
787
+ else
788
+ log.debug "Transaction traces will not be sent to the New Relic service."
789
+ end
790
+ end
791
+
792
+ # apdex_f is always 4 times the apdex_t
793
+ def apdex_f
794
+ (4 * NewRelic::Control.instance.apdex_t).to_f
795
+ end
796
+
797
+ # If the transaction threshold is set to the string
798
+ # 'apdex_f', we use 4 times the apdex_t value to record
799
+ # transactions. This gears well with using apdex since you
800
+ # will attempt to send any transactions that register as 'failing'
801
+ def apdex_f_threshold?
802
+ @transaction_sampler.config.fetch('transaction_threshold', '') =~ /apdex_f/i
803
+ end
804
+
805
+ # Sets the sql recording configuration by trying to detect
806
+ # any attempt to disable the sql collection - 'off',
807
+ # 'false', 'none', and friends. Otherwise, we accept 'raw',
808
+ # and unrecognized values default to 'obfuscated'
809
+ def set_sql_recording!
810
+ record_sql_config = @transaction_sampler.config.fetch('record_sql', :obfuscated)
811
+ case record_sql_config.to_s
812
+ when 'off'
813
+ @record_sql = :off
814
+ when 'none'
815
+ @record_sql = :off
816
+ when 'false'
817
+ @record_sql = :off
818
+ when 'raw'
819
+ @record_sql = :raw
820
+ else
821
+ @record_sql = :obfuscated
822
+ end
823
+
824
+ log_sql_transmission_warning?
825
+ end
826
+
827
+ # Warn the user when we are sending raw sql across the wire
828
+ # - they should probably be using ssl when this is true
829
+ def log_sql_transmission_warning?
830
+ log.warn("Agent is configured to send raw SQL to the service") if @record_sql == :raw
831
+ end
832
+
833
+ # Asks the collector to tell us which sub-collector we
834
+ # should be reporting to, and then does the name resolution
835
+ # on that host so we don't block on DNS during the normal
836
+ # course of agent processing
837
+ def set_collector_host!
838
+ host = invoke_remote(:get_redirect_host)
839
+ if host
840
+ @collector = control.server_from_host(host)
841
+ end
842
+ end
843
+
844
+ # Sets the collector host and connects to the server, then
845
+ # invokes the final configuration with the returned data
846
+ def query_server_for_configuration
847
+ set_collector_host!
848
+
849
+ finish_setup(connect_to_server)
850
+ end
851
+
852
+ # Takes a hash of configuration data returned from the
853
+ # server and uses it to set local variables and to
854
+ # initialize various parts of the agent that are configured
855
+ # separately.
856
+ #
857
+ # Can accommodate most arbitrary data - anything extra is
858
+ # ignored unless we say to do something with it here.
859
+ def finish_setup(config_data)
860
+ @agent_id = config_data['agent_run_id']
861
+ @report_period = config_data['data_report_period']
862
+ @url_rules = config_data['url_rules']
863
+ @beacon_configuration = BeaconConfiguration.new(config_data)
864
+ @server_side_config_enabled = config_data['listen_to_server_config']
865
+
866
+ if @server_side_config_enabled
867
+ log.info "Using config from server"
868
+ log.debug "Server provided config: #{config_data.inspect}"
869
+ end
870
+
871
+ control.merge_server_side_config(config_data) if @server_side_config_enabled
872
+ config_transaction_tracer
873
+ log_connection!(config_data)
874
+ configure_transaction_tracer!(config_data['collect_traces'], config_data['sample_rate'])
875
+ configure_error_collector!(config_data['collect_errors'])
876
+ end
877
+
878
+ # Logs when we connect to the server, for debugging purposes
879
+ # - makes sure we know if an agent has not connected
880
+ def log_connection!(config_data)
881
+ control.log! "Connected to NewRelic Service at #{@collector}"
882
+ log.debug "Agent Run = #{@agent_id}."
883
+ log.debug "Connection data = #{config_data.inspect}"
884
+ end
885
+ end
886
+ include Connect
887
+
888
+
889
+ # Serialize all the important data that the agent might want
890
+ # to send to the server. We could be sending this to file (
891
+ # common in short-running background transactions ) or
892
+ # alternately we could serialize via a pipe or socket to a
893
+ # local aggregation device
894
+ def serialize
895
+ accumulator = []
896
+ accumulator[1] = harvest_transaction_traces if @transaction_sampler
897
+ accumulator[2] = harvest_errors if @error_collector
898
+ accumulator[0] = harvest_timeslice_data
899
+ reset_stats
900
+ @metric_ids = {}
901
+ accumulator
902
+ end
903
+ public :serialize
904
+
905
+ # Accepts data as provided by the serialize method and merges
906
+ # it into our current collection of data to send. Can be
907
+ # dangerous if we re-merge the same data more than once - it
908
+ # will be sent multiple times.
909
+ def merge_data_from(data)
910
+ metrics, transaction_traces, errors = data
911
+ @stats_engine.merge_data(metrics) if metrics
912
+ if transaction_traces
913
+ if @traces
914
+ @traces = @traces + transaction_traces
915
+ else
916
+ @traces = transaction_traces
917
+ end
918
+ end
919
+ if errors
920
+ if @unsent_errors
921
+ @unsent_errors = @unsent_errors + errors
922
+ else
923
+ @unsent_errors = errors
924
+ end
925
+ end
926
+ end
927
+
928
+ public :merge_data_from
929
+
930
+ # Connect to the server and validate the license. If successful,
931
+ # @connected has true when finished. If not successful, you can
932
+ # keep calling this. Return false if we could not establish a
933
+ # connection with the server and we should not retry, such as if
934
+ # there's a bad license key.
935
+ #
936
+ # Set keep_retrying=false to disable retrying and return asap, such as when
937
+ # invoked in the foreground. Otherwise this runs until a successful
938
+ # connection is made, or the server rejects us.
939
+ #
940
+ # * <tt>:keep_retrying => false</tt> to only try to connect once, and
941
+ # return with the connection set to nil. This ensures we may try again
942
+ # later (default true).
943
+ # * <tt>force_reconnect => true</tt> if you want to establish a new connection
944
+ # to the server before running the worker loop. This means you get a separate
945
+ # agent run and New Relic sees it as a separate instance (default is false).
946
+ def connect(options)
947
+ # Don't proceed if we already connected (@connected=true) or if we tried
948
+ # to connect and were rejected with prejudice because of a license issue
949
+ # (@connected=false), unless we're forced to by force_reconnect.
950
+ return if tried_to_connect?(options)
951
+
952
+ # wait a few seconds for the web server to boot, necessary in development
953
+ @connect_retry_period = should_keep_retrying?(options) ? 10 : 0
954
+
955
+ sleep connect_retry_period
956
+ log.debug "Connecting Process to New Relic: #$0"
957
+ query_server_for_configuration
958
+ @connected_pid = $$
959
+ @connected = true
960
+ rescue NewRelic::Agent::LicenseException => e
961
+ handle_license_error(e)
962
+ rescue Timeout::Error, StandardError => e
963
+ log_error(e)
964
+ if should_retry?
965
+ retry
966
+ else
967
+ disconnect
968
+ end
969
+ end
970
+
971
+ # Who am I? Well, this method can tell you your hostname.
972
+ def determine_host
973
+ Socket.gethostname
974
+ end
975
+
976
+ # Delegates to the control class to determine the root
977
+ # directory of this project
978
+ def determine_home_directory
979
+ control.root
980
+ end
981
+
982
+ # Checks whether this process is a Passenger or Unicorn
983
+ # spawning server - if so, we probably don't intend to report
984
+ # statistics from this process
985
+ def is_application_spawner?
986
+ $0 =~ /ApplicationSpawner|^unicorn\S* master/
987
+ end
988
+
989
+ # calls the busy harvester and collects timeslice data to
990
+ # send later
991
+ def harvest_timeslice_data(time=Time.now)
992
+ # this creates timeslices that are harvested below
993
+ NewRelic::Agent::BusyCalculator.harvest_busy
994
+
995
+ @unsent_timeslice_data ||= {}
996
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
997
+ @unsent_timeslice_data
998
+ end
999
+
1000
+ # takes an array of arrays of spec and id, adds it into the
1001
+ # metric cache so we can save the collector some work by
1002
+ # sending integers instead of strings
1003
+ def fill_metric_id_cache(pairs_of_specs_and_ids)
1004
+ Array(pairs_of_specs_and_ids).each do |metric_spec, metric_id|
1005
+ @metric_ids[metric_spec] = metric_id
1006
+ end
1007
+ end
1008
+
1009
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
1010
+ # then the metric data is downsampled for another
1011
+ # transmission later
1012
+ def harvest_and_send_timeslice_data
1013
+ now = Time.now
1014
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote').record_data_point(0.0)
1015
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote/metric_data').record_data_point(0.0)
1016
+ harvest_timeslice_data(now)
1017
+ begin
1018
+ # In this version of the protocol, we get back an assoc array of spec to id.
1019
+ metric_specs_and_ids = invoke_remote(:metric_data, @agent_id,
1020
+ @last_harvest_time.to_f,
1021
+ now.to_f,
1022
+ @unsent_timeslice_data.values)
1023
+
1024
+ rescue Timeout::Error
1025
+ # assume that the data was received. chances are that it
1026
+ # was. Also, lol.
1027
+ metric_specs_and_ids = []
1028
+ end
1029
+
1030
+ fill_metric_id_cache(metric_specs_and_ids)
1031
+
1032
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
1033
+
1034
+ # if we successfully invoked this web service, then clear the unsent message cache.
1035
+ @unsent_timeslice_data = {}
1036
+ @last_harvest_time = now
1037
+ end
1038
+
1039
+ # Fills the traces array with the harvested transactions from
1040
+ # the transaction sampler, subject to the setting for slowest
1041
+ # transaction threshold
1042
+ def harvest_transaction_traces
1043
+ @traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
1044
+ @traces
1045
+ end
1046
+
1047
+ def harvest_and_send_slowest_sql
1048
+ # FIXME add the code to try to resend if our connection is down
1049
+ sql_traces = @sql_sampler.harvest
1050
+ unless sql_traces.empty?
1051
+ log.debug "Sending (#{sql_traces.size}) sql traces"
1052
+ begin
1053
+ response = invoke_remote :sql_trace_data, sql_traces
1054
+ # log.debug "Sql trace response: #{response}"
1055
+ rescue
1056
+ @sql_sampler.merge sql_traces
1057
+ end
1058
+ end
1059
+ end
1060
+
1061
+ # This handles getting the transaction traces and then sending
1062
+ # them across the wire. This includes gathering SQL
1063
+ # explanations, stripping out stack traces, and normalizing
1064
+ # SQL. note that we explain only the sql statements whose
1065
+ # segments' execution times exceed our threshold (to avoid
1066
+ # unnecessary overhead of running explains on fast queries.)
1067
+ def harvest_and_send_slowest_sample
1068
+ harvest_transaction_traces
1069
+ unless @traces.empty?
1070
+ now = Time.now
1071
+ log.debug "Sending (#{@traces.length}) transaction traces"
1072
+
1073
+ begin
1074
+ options = { :keep_backtraces => true }
1075
+ options[:record_sql] = @record_sql unless @record_sql == :off
1076
+ if @transaction_sampler.explain_enabled
1077
+ options[:explain_sql] = @transaction_sampler.explain_threshold
1078
+ end
1079
+ traces = @traces.collect {|trace| trace.prepare_to_send(options)}
1080
+ invoke_remote :transaction_sample_data, @agent_id, traces
1081
+ rescue PostTooBigException
1082
+ # we tried to send too much data, drop the first trace and
1083
+ # try again
1084
+ retry if @traces.shift
1085
+ end
1086
+
1087
+ log.debug "Sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
1088
+ end
1089
+
1090
+ # if we succeed sending this sample, then we don't need to keep
1091
+ # the slowest sample around - it has been sent already and we
1092
+ # can clear the collection and move on
1093
+ @traces = nil
1094
+ end
1095
+
1096
+ # Gets the collection of unsent errors from the error
1097
+ # collector. We pass back in an existing array of errors that
1098
+ # may be left over from a previous send
1099
+ def harvest_errors
1100
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
1101
+ @unsent_errors
1102
+ end
1103
+
1104
+ # Handles getting the errors from the error collector and
1105
+ # sending them to the server, and any error cases like trying
1106
+ # to send very large errors - we drop the oldest error on the
1107
+ # floor and try again
1108
+ def harvest_and_send_errors
1109
+ harvest_errors
1110
+ if @unsent_errors && @unsent_errors.length > 0
1111
+ log.debug "Sending #{@unsent_errors.length} errors"
1112
+ begin
1113
+ invoke_remote :error_data, @agent_id, @unsent_errors
1114
+ rescue PostTooBigException
1115
+ @unsent_errors.shift
1116
+ retry
1117
+ end
1118
+ # if the remote invocation fails, then we never clear
1119
+ # @unsent_errors, and therefore we can re-attempt to send on
1120
+ # the next heartbeat. Note the error collector maxes out at
1121
+ # 20 instances to prevent leakage
1122
+ @unsent_errors = []
1123
+ end
1124
+ end
1125
+
1126
+ # This method handles the compression of the request body that
1127
+ # we are going to send to the server
1128
+ #
1129
+ # We currently optimize for CPU here since we get roughly a 10x
1130
+ # reduction in message size with this, and CPU overhead is at a
1131
+ # premium. For extra-large posts, we use the higher compression
1132
+ # since otherwise it actually errors out.
1133
+ #
1134
+ # We do not compress if content is smaller than 64kb. There are
1135
+ # problems with bugs in Ruby in some versions that expose us
1136
+ # to a risk of segfaults if we compress aggressively.
1137
+ #
1138
+ # medium payloads get fast compression, to save CPU
1139
+ # big payloads get all the compression possible, to stay under
1140
+ # the 2,000,000 byte post threshold
1141
+ def compress_data(object)
1142
+ dump = Marshal.dump(object)
1143
+
1144
+ dump_size = dump.size
1145
+
1146
+ return [dump, 'identity'] if dump_size < (64*1024)
1147
+
1148
+ compressed_dump = Zlib::Deflate.deflate(dump, Zlib::DEFAULT_COMPRESSION)
1149
+
1150
+ # this checks to make sure mongrel won't choke on big uploads
1151
+ check_post_size(compressed_dump)
1152
+
1153
+ [compressed_dump, 'deflate']
1154
+ end
1155
+
1156
+ # Raises a PostTooBigException if the post_string is longer
1157
+ # than the limit configured in the control object
1158
+ def check_post_size(post_string)
1159
+ # TODO: define this as a config option on the server side
1160
+ return if post_string.size < control.post_size_limit
1161
+ log.warn "Tried to send too much data: #{post_string.size} bytes"
1162
+ raise PostTooBigException
1163
+ end
1164
+
1165
+ # Posts to the specified server
1166
+ #
1167
+ # Options:
1168
+ # - :uri => the path to request on the server (a misnomer of
1169
+ # course)
1170
+ # - :encoding => the encoding to pass to the server
1171
+ # - :collector => a URI object that responds to the 'name' method
1172
+ # and returns the name of the collector to
1173
+ # contact
1174
+ # - :data => the data to send as the body of the request
1175
+ def send_request(opts)
1176
+ request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'HOST' => opts[:collector].name)
1177
+ request['user-agent'] = user_agent
1178
+ request.content_type = "application/octet-stream"
1179
+ request.body = opts[:data]
1180
+
1181
+ log.debug "Connect to #{opts[:collector]}#{opts[:uri]}"
1182
+
1183
+ response = nil
1184
+ http = control.http_connection(collector)
1185
+ http.read_timeout = nil
1186
+ begin
1187
+ NewRelic::TimerLib.timeout(@request_timeout) do
1188
+ response = http.request(request)
1189
+ end
1190
+ rescue Timeout::Error
1191
+ log.warn "Timed out trying to post data to New Relic (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
1192
+ raise
1193
+ end
1194
+ if response.is_a? Net::HTTPServiceUnavailable
1195
+ raise NewRelic::Agent::ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}"
1196
+ elsif response.is_a? Net::HTTPGatewayTimeOut
1197
+ log.debug("Timed out getting response: #{response.message}")
1198
+ raise Timeout::Error, response.message
1199
+ elsif response.is_a? Net::HTTPRequestEntityTooLarge
1200
+ raise PostTooBigException
1201
+ elsif !(response.is_a? Net::HTTPSuccess)
1202
+ raise NewRelic::Agent::ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}"
1203
+ end
1204
+ response
1205
+ end
1206
+
1207
+ # Decompresses the response from the server, if it is gzip
1208
+ # encoded, otherwise returns it verbatim
1209
+ def decompress_response(response)
1210
+ if response['content-encoding'] != 'gzip'
1211
+ log.debug "Uncompressed content returned"
1212
+ return response.body
1213
+ end
1214
+ log.debug "Decompressing return value"
1215
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
1216
+ i.read
1217
+ end
1218
+
1219
+ # unmarshals the response and raises it if it is an exception,
1220
+ # so we can handle it in nonlocally
1221
+ def check_for_exception(response)
1222
+ dump = decompress_response(response)
1223
+ value = Marshal.load(dump)
1224
+ raise value if value.is_a? Exception
1225
+ value
1226
+ end
1227
+
1228
+ # The path on the server that we should post our data to
1229
+ def remote_method_uri(method)
1230
+ uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
1231
+ uri << "?run_id=#{@agent_id}" if @agent_id
1232
+ uri
1233
+ end
1234
+
1235
+ # Sets the user agent for connections to the server, to
1236
+ # conform with the HTTP spec and allow for debugging. Includes
1237
+ # the ruby version and also zlib version if available since
1238
+ # that may cause corrupt compression if there is a problem.
1239
+ def user_agent
1240
+ ruby_description = ''
1241
+ # note the trailing space!
1242
+ ruby_description << "(ruby #{::RUBY_VERSION} #{::RUBY_PLATFORM}) " if defined?(::RUBY_VERSION) && defined?(::RUBY_PLATFORM)
1243
+ zlib_version = ''
1244
+ zlib_version << "zlib/#{Zlib.zlib_version}" if defined?(::Zlib) && Zlib.respond_to?(:zlib_version)
1245
+ "NewRelic-RubyAgent/#{NewRelic::VERSION::STRING} #{ruby_description}#{zlib_version}"
1246
+ end
1247
+
1248
+ # send a message via post to the actual server. This attempts
1249
+ # to automatically compress the data via zlib if it is large
1250
+ # enough to be worth compressing, and handles any errors the
1251
+ # server may return
1252
+ def invoke_remote(method, *args)
1253
+ now = Time.now
1254
+ #determines whether to zip the data or send plain
1255
+ post_data, encoding = compress_data(args)
1256
+
1257
+ response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
1258
+
1259
+ # raises the right exception if the remote server tells it to die
1260
+ return check_for_exception(response)
1261
+ rescue NewRelic::Agent::ForceRestartException => e
1262
+ log.info e.message
1263
+ raise
1264
+ rescue SystemCallError, SocketError => e
1265
+ # These include Errno connection errors
1266
+ raise NewRelic::Agent::ServerConnectionException, "Recoverable error connecting to the server: #{e}"
1267
+ ensure
1268
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote').record_data_point((Time.now - now).to_f)
1269
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote/' + method.to_s).record_data_point((Time.now - now).to_f)
1270
+ end
1271
+
1272
+ def save_or_transmit_data
1273
+ if NewRelic::DataSerialization.should_send_data?
1274
+ log.debug "Sending data to New Relic Service"
1275
+ NewRelic::Agent.load_data unless NewRelic::Control.instance.disable_serialization?
1276
+ harvest_and_send_errors
1277
+ harvest_and_send_slowest_sample
1278
+ harvest_and_send_slowest_sql
1279
+ harvest_and_send_timeslice_data
1280
+ else
1281
+ log.debug "Serializing agent data to disk"
1282
+ NewRelic::Agent.save_data
1283
+ end
1284
+ rescue => e
1285
+ NewRelic::Control.instance.disable_serialization = true
1286
+ NewRelic::Control.instance.log.warn("Disabling serialization: #{e.message}")
1287
+ retry_count ||= 0
1288
+ retry_count += 1
1289
+ retry unless retry_count > 1
1290
+ raise e
1291
+ end
1292
+
1293
+ # This method contacts the server to send remaining data and
1294
+ # let the server know that the agent is shutting down - this
1295
+ # allows us to do things like accurately set the end of the
1296
+ # lifetime of the process
1297
+ #
1298
+ # If this process comes from a parent process, it will not
1299
+ # disconnect, so that the parent process can continue to send data
1300
+ def graceful_disconnect
1301
+ if @connected
1302
+ begin
1303
+ @request_timeout = 10
1304
+ save_or_transmit_data
1305
+ if @connected_pid == $$
1306
+ log.debug "Sending New Relic service agent run shutdown message"
1307
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
1308
+ else
1309
+ log.debug "This agent connected from parent process #{@connected_pid}--not sending shutdown"
1310
+ end
1311
+ log.debug "Graceful disconnect complete"
1312
+ rescue Timeout::Error, StandardError
1313
+ end
1314
+ else
1315
+ log.debug "Bypassing graceful disconnect - agent not connected"
1316
+ end
1317
+ end
1318
+ end
1319
+
1320
+ extend ClassMethods
1321
+ include InstanceMethods
1322
+ include BrowserMonitoring
1323
+ end
1324
+ end
1325
+ end