dolores_rpm 3.2.0.2

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