factorylabs-newrelic_rpm 2.10.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. data/CHANGELOG +354 -0
  2. data/LICENSE +37 -0
  3. data/README-2.10.2.2 +10 -0
  4. data/README.md +138 -0
  5. data/bin/mongrel_rpm +33 -0
  6. data/bin/newrelic_cmd +4 -0
  7. data/cert/cacert.pem +34 -0
  8. data/install.rb +45 -0
  9. data/lib/new_relic/agent.rb +315 -0
  10. data/lib/new_relic/agent/agent.rb +647 -0
  11. data/lib/new_relic/agent/busy_calculator.rb +86 -0
  12. data/lib/new_relic/agent/chained_call.rb +13 -0
  13. data/lib/new_relic/agent/collection_helper.rb +66 -0
  14. data/lib/new_relic/agent/error_collector.rb +117 -0
  15. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  16. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +91 -0
  17. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  18. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +389 -0
  19. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  20. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +12 -0
  21. data/lib/new_relic/agent/instrumentation/memcache.rb +24 -0
  22. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  23. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  24. data/lib/new_relic/agent/instrumentation/metric_frame.rb +199 -0
  25. data/lib/new_relic/agent/instrumentation/net.rb +21 -0
  26. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  27. data/lib/new_relic/agent/instrumentation/rack.rb +109 -0
  28. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  29. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  30. data/lib/new_relic/agent/instrumentation/rails/errors.rb +24 -0
  31. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  32. data/lib/new_relic/agent/method_tracer.rb +348 -0
  33. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  34. data/lib/new_relic/agent/sampler.rb +46 -0
  35. data/lib/new_relic/agent/samplers/cpu_sampler.rb +50 -0
  36. data/lib/new_relic/agent/samplers/memory_sampler.rb +141 -0
  37. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +23 -0
  38. data/lib/new_relic/agent/samplers/object_sampler.rb +24 -0
  39. data/lib/new_relic/agent/shim_agent.rb +21 -0
  40. data/lib/new_relic/agent/stats_engine.rb +22 -0
  41. data/lib/new_relic/agent/stats_engine/metric_stats.rb +116 -0
  42. data/lib/new_relic/agent/stats_engine/samplers.rb +74 -0
  43. data/lib/new_relic/agent/stats_engine/transactions.rb +154 -0
  44. data/lib/new_relic/agent/transaction_sampler.rb +315 -0
  45. data/lib/new_relic/agent/worker_loop.rb +118 -0
  46. data/lib/new_relic/commands/deployments.rb +145 -0
  47. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  48. data/lib/new_relic/control.rb +484 -0
  49. data/lib/new_relic/control/external.rb +13 -0
  50. data/lib/new_relic/control/merb.rb +24 -0
  51. data/lib/new_relic/control/rails.rb +151 -0
  52. data/lib/new_relic/control/ruby.rb +36 -0
  53. data/lib/new_relic/control/sinatra.rb +14 -0
  54. data/lib/new_relic/histogram.rb +89 -0
  55. data/lib/new_relic/local_environment.rb +299 -0
  56. data/lib/new_relic/merbtasks.rb +6 -0
  57. data/lib/new_relic/metric_data.rb +42 -0
  58. data/lib/new_relic/metric_parser.rb +124 -0
  59. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  60. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  61. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  62. data/lib/new_relic/metric_parser/controller.rb +54 -0
  63. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  64. data/lib/new_relic/metric_parser/errors.rb +6 -0
  65. data/lib/new_relic/metric_parser/external.rb +50 -0
  66. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  67. data/lib/new_relic/metric_parser/other_transaction.rb +15 -0
  68. data/lib/new_relic/metric_parser/view.rb +61 -0
  69. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  70. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  71. data/lib/new_relic/metric_spec.rb +67 -0
  72. data/lib/new_relic/metrics.rb +7 -0
  73. data/lib/new_relic/noticed_error.rb +23 -0
  74. data/lib/new_relic/rack/metric_app.rb +56 -0
  75. data/lib/new_relic/rack/mongrel_rpm.ru +25 -0
  76. data/lib/new_relic/rack/newrelic.yml +26 -0
  77. data/lib/new_relic/rack_app.rb +5 -0
  78. data/lib/new_relic/recipes.rb +82 -0
  79. data/lib/new_relic/stats.rb +362 -0
  80. data/lib/new_relic/transaction_analysis.rb +121 -0
  81. data/lib/new_relic/transaction_sample.rb +671 -0
  82. data/lib/new_relic/version.rb +54 -0
  83. data/lib/new_relic_api.rb +276 -0
  84. data/lib/newrelic_rpm.rb +45 -0
  85. data/lib/tasks/all.rb +4 -0
  86. data/lib/tasks/install.rake +7 -0
  87. data/lib/tasks/tests.rake +15 -0
  88. data/newrelic.yml +227 -0
  89. data/newrelic_rpm.gemspec +214 -0
  90. data/recipes/newrelic.rb +6 -0
  91. data/test/active_record_fixtures.rb +55 -0
  92. data/test/config/newrelic.yml +46 -0
  93. data/test/config/test_control.rb +38 -0
  94. data/test/new_relic/agent/active_record_instrumentation_test.rb +268 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +254 -0
  96. data/test/new_relic/agent/agent_test_controller.rb +78 -0
  97. data/test/new_relic/agent/busy_calculator_test.rb +79 -0
  98. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  99. data/test/new_relic/agent/collection_helper_test.rb +125 -0
  100. data/test/new_relic/agent/error_collector_test.rb +173 -0
  101. data/test/new_relic/agent/method_tracer_test.rb +340 -0
  102. data/test/new_relic/agent/metric_data_test.rb +56 -0
  103. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  104. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  105. data/test/new_relic/agent/net_instrumentation_test.rb +63 -0
  106. data/test/new_relic/agent/rpm_agent_test.rb +125 -0
  107. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  108. data/test/new_relic/agent/stats_engine/samplers_test.rb +88 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +184 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +177 -0
  111. data/test/new_relic/agent/testable_agent.rb +13 -0
  112. data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
  113. data/test/new_relic/agent/transaction_sample_test.rb +186 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +404 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +110 -0
  117. data/test/new_relic/delayed_job_test.rb +108 -0
  118. data/test/new_relic/deployments_api_test.rb +68 -0
  119. data/test/new_relic/environment_test.rb +75 -0
  120. data/test/new_relic/metric_parser_test.rb +172 -0
  121. data/test/new_relic/metric_spec_test.rb +177 -0
  122. data/test/new_relic/shim_agent_test.rb +9 -0
  123. data/test/new_relic/stats_test.rb +291 -0
  124. data/test/new_relic/version_number_test.rb +76 -0
  125. data/test/test_helper.rb +53 -0
  126. data/test/ui/newrelic_controller_test.rb +14 -0
  127. data/test/ui/newrelic_helper_test.rb +53 -0
  128. data/ui/controllers/newrelic_controller.rb +220 -0
  129. data/ui/helpers/google_pie_chart.rb +49 -0
  130. data/ui/helpers/newrelic_helper.rb +320 -0
  131. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  132. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  133. data/ui/views/newrelic/_sample.rhtml +19 -0
  134. data/ui/views/newrelic/_segment.rhtml +28 -0
  135. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  136. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  137. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  138. data/ui/views/newrelic/_show_sample_sql.rhtml +20 -0
  139. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  140. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  141. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  142. data/ui/views/newrelic/_table.rhtml +12 -0
  143. data/ui/views/newrelic/explain_sql.rhtml +42 -0
  144. data/ui/views/newrelic/images/arrow-close.png +0 -0
  145. data/ui/views/newrelic/images/arrow-open.png +0 -0
  146. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  147. data/ui/views/newrelic/images/file_icon.png +0 -0
  148. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  149. data/ui/views/newrelic/images/new-relic-rpm-desktop.gif +0 -0
  150. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  151. data/ui/views/newrelic/images/textmate.png +0 -0
  152. data/ui/views/newrelic/index.rhtml +57 -0
  153. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  154. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  155. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  156. data/ui/views/newrelic/show_sample.rhtml +80 -0
  157. data/ui/views/newrelic/show_source.rhtml +3 -0
  158. data/ui/views/newrelic/stylesheets/style.css +484 -0
  159. data/ui/views/newrelic/threads.rhtml +52 -0
  160. metadata +238 -0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rack'
4
+ require 'rack/handler/mongrel'
5
+ require 'optparse'
6
+
7
+ port = 3000
8
+ options = { }
9
+ appname = nil
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{File.basename($0)} [options] [app_name]"
12
+ opts.on("-p", "--port=port", Integer, "default: #{port}") { | port | }
13
+ opts.on("--[no-]logging", "turn off request logging" ) { | l | options[:logging] = l }
14
+ opts.on("--license=rpm_license_key", "override license key" ) { | l | options[:license_key] = l }
15
+ opts.on("--install", "install a newrelic.yml template" ) { | l | options[:install] = true }
16
+ opts.separator ""
17
+ opts.separator "app_name is the name of the application where the metrics will be stored"
18
+ opts.separator ""
19
+ # The rackup file references this var
20
+ appname = opts.parse!(ARGV.clone).first
21
+ end
22
+
23
+ options[:app_name] = appname if appname
24
+
25
+ ru_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "new_relic", "rack", "mongrel_rpm.ru"))
26
+ rackup_code = File.read ru_file
27
+ builder = Rack::Builder.new { eval rackup_code, binding, ru_file }
28
+
29
+ options = { :Host => '127.0.0.1', :Port => port }
30
+ Rack::Handler::Mongrel.run(builder.to_app, options) do | server |
31
+ NewRelic::Control.instance.log! "Started Mongrel listening for '#{NewRelic::Control.instance.app_names.join(" and ")}' data at #{server.host}:#{server.port}"
32
+ end
33
+
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # executes one of the commands in the new_relic/commands directory
3
+ # pass the name of the command as an argument
4
+ require File.dirname(__FILE__) + '/../lib/new_relic/commands/new_relic_commands'
@@ -0,0 +1,34 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
3
+ b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
4
+ YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
5
+ bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
6
+ MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
7
+ d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
8
+ UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
9
+ LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
10
+ A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
11
+ GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
12
+ DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
13
+ lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
14
+ icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
15
+ Orf1LXLI
16
+ -----END CERTIFICATE-----
17
+
18
+ -----BEGIN CERTIFICATE-----
19
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
20
+ b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
21
+ YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
22
+ bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
23
+ MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
24
+ d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
25
+ UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
26
+ LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
27
+ A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
28
+ CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
29
+ ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
30
+ SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
31
+ UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
32
+ W9ViH0Pd
33
+ -----END CERTIFICATE-----
34
+
@@ -0,0 +1,45 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+
4
+ def install_newrelic_config_file(license_key="PASTE_YOUR_LICENSE_KEY_HERE")
5
+ # Install a newrelic.yml file into the local config directory.
6
+ if File.directory? "config"
7
+ dest_dir = "config"
8
+ else
9
+ dest_dir = File.join(ENV["HOME"],".newrelic") rescue nil
10
+ FileUtils.mkdir(dest_dir) if dest_dir
11
+ end
12
+
13
+ src_config_file = File.join(File.dirname(__FILE__),"newrelic.yml")
14
+ dest_config_file = File.join(dest_dir, "newrelic.yml") if dest_dir
15
+
16
+ if !dest_dir
17
+ STDERR.puts "Could not find a config or ~/.newrelic directory to locate the default newrelic.yml file"
18
+ elsif File::exists? dest_config_file
19
+ STDERR.puts "\nA config file already exists at #{dest_config_file}.\n"
20
+ else
21
+ generated_for_user = ""
22
+ yaml = ERB.new(File.read(src_config_file)).result(binding)
23
+ File.open( dest_config_file, 'w' ) do |out|
24
+ out.puts yaml
25
+ end
26
+
27
+ puts <<-EOF
28
+
29
+ Installed a default configuration file in #{dest_dir}.
30
+
31
+ To monitor your application in production mode, sign up for an account
32
+ at www.newrelic.com, and replace the newrelic.yml file with the one
33
+ you receive upon registration.
34
+
35
+ Please review the README.md file for more information.
36
+
37
+ E-mail support@newrelic.com with any problems or questions.
38
+
39
+ EOF
40
+ end
41
+ end
42
+
43
+ if __FILE__ == $0 || $0 =~ /script\/plugin/
44
+ install_newrelic_config_file
45
+ end
@@ -0,0 +1,315 @@
1
+ # = New Relic Agent
2
+ #
3
+ # New Relic RPM is a performance monitoring application for Ruby
4
+ # applications running in production. For more information on RPM
5
+ # please visit http://www.newrelic.com.
6
+ #
7
+ # The New Relic Agent can be installed in Rails applications to gather
8
+ # runtime performance metrics, traces, and errors for display in a
9
+ # Developer Mode UI (mapped to /newrelic in your application server)
10
+ # or for monitoring and analysis at http://rpm.newrelic.com with just
11
+ # about any Ruby application.
12
+ #
13
+ # For detailed information on configuring or customizing the RPM Agent
14
+ # please visit our {support and documentation site}[http://support.newrelic.com].
15
+ #
16
+ # == Starting the Agent as a Gem
17
+ #
18
+ # For Rails, add:
19
+ # config.gem 'newrelic_rpm'
20
+ # to your initialization sequence.
21
+ #
22
+ # For merb, do
23
+ # dependency 'newrelic_rpm'
24
+ # in the Merb config/init.rb
25
+ #
26
+ # For Sinatra, just require the +newrelic_rpm+ gem and it will
27
+ # automatically detect Sinatra and instrument all the handlers.
28
+ #
29
+ # For other frameworks, or to manage the agent manually,
30
+ # invoke NewRelic::Agent#manual_start directly.
31
+ #
32
+ # == Configuring the Agent
33
+ #
34
+ # All agent configuration is done in the <tt>newrelic.yml</tt> file. This
35
+ # file is by default read from the +config+ directory of the
36
+ # application root and is subsequently searched for in the application
37
+ # root directory, and then in a <tt>~/.newrelic</tt> directory
38
+ #
39
+ # == Using with Rack/Metal
40
+ #
41
+ # To instrument middlewares, refer to the docs in
42
+ # NewRelic::Agent::Instrumentation::Rack.
43
+ #
44
+ # == Agent API
45
+ #
46
+ # For details on the Agent API, refer to NewRelic::Agent.
47
+ #
48
+ #
49
+ # :main: lib/new_relic/agent.rb
50
+ module NewRelic
51
+ # == Agent APIs
52
+ # This module contains the public API methods for the Agent.
53
+ #
54
+ # For adding custom instrumentation to method invocations, refer to
55
+ # the docs in the class NewRelic::Agent::MethodTracer.
56
+ #
57
+ # For information on how to customize the controller
58
+ # instrumentation, or to instrument something other than Rails so
59
+ # that high level dispatcher actions or background tasks show up as
60
+ # first class operations in RPM, refer to
61
+ # NewRelic::Agent::Instrumentation::ControllerInstrumentation and
62
+ # NewRelic::Agent::Instrumentation::ControllerInstrumentation::ClassMethods.
63
+ #
64
+ # Methods in this module as well as documented methods in
65
+ # NewRelic::Agent::MethodTracer and
66
+ # NewRelic::Agent::Instrumentation::ControllerInstrumentation are
67
+ # available to applications. When the agent is not enabled the
68
+ # method implementations are stubbed into no-ops to reduce overhead.
69
+ #
70
+ # Methods and classes in other parts of the agent are not guaranteed
71
+ # to be available between releases.
72
+ #
73
+ # Refer to the online docs at support.newrelic.com to see how to
74
+ # access the data collected by custom instrumentation, or e-mail
75
+ # support at New Relic for help.
76
+ module Agent
77
+ extend self
78
+
79
+ require 'new_relic/version'
80
+ require 'new_relic/local_environment'
81
+ require 'new_relic/stats'
82
+ require 'new_relic/metric_spec'
83
+ require 'new_relic/metric_data'
84
+ require 'new_relic/metric_parser'
85
+ require 'new_relic/transaction_analysis'
86
+ require 'new_relic/transaction_sample'
87
+ require 'new_relic/noticed_error'
88
+ require 'new_relic/histogram'
89
+
90
+ require 'new_relic/agent/chained_call'
91
+ require 'new_relic/agent/agent'
92
+ require 'new_relic/agent/shim_agent'
93
+ require 'new_relic/agent/method_tracer'
94
+ require 'new_relic/agent/worker_loop'
95
+ require 'new_relic/agent/stats_engine'
96
+ require 'new_relic/agent/collection_helper'
97
+ require 'new_relic/agent/transaction_sampler'
98
+ require 'new_relic/agent/error_collector'
99
+ require 'new_relic/agent/busy_calculator'
100
+ require 'new_relic/agent/sampler'
101
+
102
+ require 'new_relic/agent/samplers/cpu_sampler'
103
+ require 'new_relic/agent/samplers/memory_sampler'
104
+ require 'new_relic/agent/samplers/mongrel_sampler'
105
+ require 'new_relic/agent/samplers/object_sampler'
106
+ require 'set'
107
+ require 'sync'
108
+ require 'thread'
109
+ require 'resolv'
110
+ require 'timeout'
111
+
112
+ # An exception that is thrown by the server if the agent license is invalid.
113
+ class LicenseException < StandardError; end
114
+
115
+ # An exception that forces an agent to stop reporting until its mongrel is restarted.
116
+ class ForceDisconnectException < StandardError; end
117
+
118
+ # An exception that forces an agent to restart.
119
+ class ForceRestartException < StandardError; end
120
+
121
+ # Used to blow out of a periodic task without logging a an error, such as for routine
122
+ # failures.
123
+ class IgnoreSilentlyException < StandardError; end
124
+
125
+ # Used for when a transaction trace or error report has too much
126
+ # data, so we reset the queue to clear the extra-large item
127
+ class PostTooBigException < IgnoreSilentlyException; end
128
+
129
+ # Reserved for future use. Meant to represent a problem on the server side.
130
+ class ServerError < StandardError; end
131
+
132
+ class BackgroundLoadingError < StandardError; end
133
+
134
+ @agent = nil
135
+
136
+ # The singleton Agent instance. Used internally.
137
+ def agent #:nodoc:
138
+ raise "Plugin not initialized!" if @agent.nil?
139
+ @agent
140
+ end
141
+
142
+ def agent= new_instance #:nodoc:
143
+ @agent = new_instance
144
+ end
145
+
146
+ alias instance agent #:nodoc:
147
+
148
+ # Get or create a statistics gatherer that will aggregate numerical data
149
+ # under a metric name.
150
+ #
151
+ # +metric_name+ should follow a slash separated path convention. Application
152
+ # specific metrics should begin with "Custom/".
153
+ #
154
+ # Return a NewRelic::Stats that accepts data
155
+ # via calls to add_data_point(value).
156
+ def get_stats(metric_name, use_scope=false)
157
+ @agent.stats_engine.get_stats(metric_name, use_scope)
158
+ end
159
+
160
+ alias get_stats_no_scope get_stats
161
+
162
+ # Call this to manually start the Agent in situations where the Agent does
163
+ # not auto-start.
164
+ #
165
+ # When the app environment loads, so does the Agent. However, the Agent will
166
+ # only connect to RPM if a web front-end is found. If you want to selectively monitor
167
+ # ruby processes that don't use web plugins, then call this method in your
168
+ # code and the Agent will fire up and start reporting to RPM.
169
+ #
170
+ # Options are passed in as overrides for values in the newrelic.yml, such
171
+ # as app_name. In addition, the option +log+ will take a logger that
172
+ # will be used instead of the standard file logger. The setting for
173
+ # the newrelic.yml section to use (ie, RAILS_ENV) can be overridden
174
+ # with an :env argument.
175
+ #
176
+ def manual_start(options={})
177
+ raise unless Hash === options
178
+ # Ignore all args but hash options
179
+ options.merge! :agent_enabled => true
180
+ NewRelic::Control.instance.init_plugin options
181
+ end
182
+
183
+ # Shutdown the agent. Call this before exiting. Sends any queued data
184
+ # and kills the background thread.
185
+ def shutdown
186
+ @agent.shutdown
187
+ end
188
+
189
+ # Add instrumentation files to the agent. The argument should be a glob
190
+ # matching ruby scripts which will be executed at the time instrumentation
191
+ # is loaded. Since instrumentation is not loaded when the agent is not
192
+ # running it's better to use this method to register instrumentation than
193
+ # just loading the files directly, although that probably also works.
194
+ def add_instrumentation file_pattern
195
+ NewRelic::Control.instance.add_instrumentation file_pattern
196
+ end
197
+
198
+ # This method sets the block sent to this method as a sql obfuscator.
199
+ # The block will be called with a single String SQL statement to obfuscate.
200
+ # The method must return the obfuscated String SQL.
201
+ # If chaining of obfuscators is required, use type = :before or :after
202
+ #
203
+ # type = :before, :replace, :after
204
+ #
205
+ # Example:
206
+ #
207
+ # NewRelic::Agent.set_sql_obfuscator(:replace) do |sql|
208
+ # my_obfuscator(sql)
209
+ # end
210
+ #
211
+ def set_sql_obfuscator(type = :replace, &block)
212
+ agent.set_sql_obfuscator type, &block
213
+ end
214
+
215
+
216
+ # This method sets the state of sql recording in the transaction
217
+ # sampler feature. Within the given block, no sql will be recorded
218
+ #
219
+ # usage:
220
+ #
221
+ # NewRelic::Agent.disable_sql_recording do
222
+ # ...
223
+ # end
224
+ #
225
+ def disable_sql_recording
226
+ state = agent.set_record_sql(false)
227
+ begin
228
+ yield
229
+ ensure
230
+ agent.set_record_sql(state)
231
+ end
232
+ end
233
+
234
+ # This method disables the recording of transaction traces in the given
235
+ # block. See also #disable_all_tracing
236
+ def disable_transaction_tracing
237
+ state = agent.set_record_tt(false)
238
+ begin
239
+ yield
240
+ ensure
241
+ agent.set_record_tt(state)
242
+ end
243
+ end
244
+
245
+ # Cancel the collection of the current transaction in progress, if any.
246
+ # Only affects the transaction started on this thread once it has started
247
+ # and before it has completed.
248
+ def abort_transaction!
249
+ # The class may not be loaded if the agent is disabled
250
+ if defined? NewRelic::Agent::Instrumentation::MetricFrame
251
+ NewRelic::Agent::Instrumentation::MetricFrame.abort_transaction!
252
+ end
253
+ end
254
+
255
+ # Yield to the block without collecting any metrics or traces in any of the
256
+ # subsequent calls. If executed recursively, will keep track of the first
257
+ # entry point and turn on tracing again after leaving that block.
258
+ # This uses the thread local +newrelic_untrace+
259
+ def disable_all_tracing
260
+ agent.push_trace_execution_flag(false)
261
+ yield
262
+ ensure
263
+ agent.pop_trace_execution_flag
264
+ end
265
+
266
+ # Check to see if we are capturing metrics currently on this thread.
267
+ def is_execution_traced?
268
+ Thread.current[:newrelic_untraced].nil? || Thread.current[:newrelic_untraced].last != false
269
+ end
270
+
271
+ # Set a filter to be applied to errors that RPM will track.
272
+ # The block should return the exception to track (which could be different from
273
+ # the original exception) or nil to ignore this exception.
274
+ #
275
+ # The block is yielded to with the exception to filter.
276
+ #
277
+ def ignore_error_filter(&block)
278
+ agent.error_collector.ignore_error_filter(&block)
279
+ end
280
+
281
+ # Record the given error in RPM. It will be passed through the #ignore_error_filter
282
+ # if there is one.
283
+ #
284
+ # * <tt>exception</tt> is the exception which will be recorded
285
+ # * <tt>extra_params</tt> is a hash of name value pairs to appear alongside
286
+ # the exception in RPM.
287
+ #
288
+ def notice_error(exception, extra_params = {})
289
+ NewRelic::Agent::Instrumentation::MetricFrame.notice_error(exception, extra_params)
290
+ end
291
+
292
+ # Add parameters to the current transaction trace on the call stack.
293
+ #
294
+ def add_custom_parameters(params)
295
+ agent.add_custom_parameters(params)
296
+ end
297
+
298
+ alias add_request_parameters add_custom_parameters
299
+
300
+ # Yield to a block that is run with a database metric name context. This means
301
+ # the Database instrumentation will use this for the metric name if it does not
302
+ # otherwise know about a model. This is re-entrant.
303
+ #
304
+ # * <tt>model</tt> is the DB model class
305
+ # * <tt>method</tt> is the name of the finder method or other method to identify the operation with.
306
+ #
307
+ def with_database_metric_name(model, method, &block)
308
+ if frame = NewRelic::Agent::Instrumentation::MetricFrame.current
309
+ frame.with_database_metric_name(model, method, &block)
310
+ else
311
+ yield
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,647 @@
1
+ require 'socket'
2
+ require 'net/https'
3
+ require 'net/http'
4
+ require 'logger'
5
+ require 'zlib'
6
+ require 'stringio'
7
+
8
+ # The NewRelic Agent collects performance data from ruby applications
9
+ # in realtime as the application runs, and periodically sends that
10
+ # data to the NewRelic server.
11
+ module NewRelic::Agent
12
+
13
+ # The Agent is a singleton that is instantiated when the plugin is
14
+ # activated.
15
+ class Agent
16
+
17
+ # Specifies the version of the agent's communication protocol with
18
+ # the NewRelic hosted site.
19
+
20
+ PROTOCOL_VERSION = 8
21
+
22
+ attr_reader :obfuscator
23
+ attr_reader :stats_engine
24
+ attr_reader :transaction_sampler
25
+ attr_reader :error_collector
26
+ attr_reader :task_loop
27
+ attr_reader :record_sql
28
+ attr_reader :histogram
29
+ attr_reader :metric_ids
30
+ attr_reader :should_send_errors
31
+
32
+ # Should only be called by NewRelic::Control
33
+ def self.instance
34
+ @instance ||= self.new
35
+ end
36
+ # This method is deprecated. Use NewRelic::Agent.manual_start
37
+ def manual_start(ignored=nil, also_ignored=nil)
38
+ raise "This method no longer supported. Instead use the class method NewRelic::Agent.manual_start"
39
+ end
40
+
41
+ # this method makes sure that the agent is running. it's important
42
+ # for passenger where processes are forked and the agent is
43
+ # dormant
44
+ #
45
+ def ensure_worker_thread_started
46
+ return unless control.agent_enabled? && control.monitor_mode? && !@invalid_license
47
+ if !running?
48
+ log.info "Detected that the worker loop is not running. Restarting."
49
+ # Assume we've been forked, clear out stats that are left over from parent process
50
+ reset_stats
51
+ launch_worker_thread
52
+ @stats_engine.spawn_sampler_thread
53
+ end
54
+ end
55
+
56
+ # True if the worker thread has been started. Doesn't necessarily
57
+ # mean we are connected
58
+ def running?
59
+ control.agent_enabled? && control.monitor_mode? && @task_loop && @task_loop.pid == $$
60
+ end
61
+
62
+ # True if we have initialized and completed 'start'
63
+ def started?
64
+ @started
65
+ end
66
+
67
+ # Attempt a graceful shutdown of the agent.
68
+ def shutdown
69
+ return if not started?
70
+ if @task_loop
71
+ @task_loop.stop
72
+
73
+ log.debug "Starting Agent shutdown"
74
+
75
+ # if litespeed, then ignore all future SIGUSR1 - it's
76
+ # litespeed trying to shut us down
77
+
78
+ if control.dispatcher == :litespeed
79
+ Signal.trap("SIGUSR1", "IGNORE")
80
+ Signal.trap("SIGTERM", "IGNORE")
81
+ end
82
+
83
+ begin
84
+ graceful_disconnect
85
+ rescue => e
86
+ log.error e
87
+ log.error e.backtrace.join("\n")
88
+ end
89
+ end
90
+ @started = nil
91
+ end
92
+
93
+ def start_transaction
94
+ Thread::current[:custom_params] = nil
95
+ @stats_engine.start_transaction
96
+ end
97
+
98
+ def end_transaction
99
+ Thread::current[:custom_params] = nil
100
+ @stats_engine.end_transaction
101
+ end
102
+
103
+ def set_record_sql(should_record)
104
+ prev = Thread::current[:record_sql]
105
+ Thread::current[:record_sql] = should_record
106
+ prev.nil? || prev
107
+ end
108
+
109
+ def set_record_tt(should_record)
110
+ prev = Thread::current[:record_tt]
111
+ Thread::current[:record_tt] = should_record
112
+ prev.nil? || prev
113
+ end
114
+ # Push flag indicating whether we should be tracing in this
115
+ # thread.
116
+ def push_trace_execution_flag(should_trace=false)
117
+ (Thread.current[:newrelic_untraced] ||= []) << should_trace
118
+ end
119
+
120
+ # Pop the current trace execution status. Restore trace execution status
121
+ # to what it was before we pushed the current flag.
122
+ def pop_trace_execution_flag
123
+ Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
124
+ end
125
+
126
+ def add_custom_parameters(params)
127
+ p = Thread::current[:custom_params] || (Thread::current[:custom_params] = {})
128
+
129
+ p.merge!(params)
130
+ end
131
+
132
+ def custom_params
133
+ Thread::current[:custom_params] || {}
134
+ end
135
+
136
+ def set_sql_obfuscator(type, &block)
137
+ if type == :before
138
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
139
+ elsif type == :after
140
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
141
+ elsif type == :replace
142
+ @obfuscator = block
143
+ else
144
+ fail "unknown sql_obfuscator type #{type}"
145
+ end
146
+ end
147
+
148
+ def log
149
+ NewRelic::Control.instance.log
150
+ end
151
+
152
+ # Start up the agent. This verifies that the agent_enabled? is
153
+ # true and initializes the sampler based on the current
154
+ # controluration settings. Then it will fire up the background
155
+ # thread for sending data to the server if applicable.
156
+ def start
157
+ if started?
158
+ control.log! "Agent Started Already!", :error
159
+ return
160
+ end
161
+ return if !control.agent_enabled?
162
+
163
+ @local_host = determine_host
164
+
165
+ log.info "Web container: #{control.dispatcher.to_s}"
166
+
167
+ @started = true
168
+
169
+ sampler_config = control.fetch('transaction_tracer', {})
170
+ @use_transaction_sampler = sampler_config.fetch('enabled', true)
171
+
172
+ @record_sql = sampler_config.fetch('record_sql', :obfuscated).to_sym
173
+
174
+ # use transaction_threshold: 4.0 to force the TT collection
175
+ # threshold to 4 seconds
176
+ # use transaction_threshold: apdex_f to use your apdex t value
177
+ # multiplied by 4
178
+ # undefined transaction_threshold defaults to 2.0
179
+ apdex_f = 4 * NewRelic::Control.instance.apdex_t
180
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0)
181
+ if @slowest_transaction_threshold =~ /apdex_f/i
182
+ @slowest_transaction_threshold = apdex_f
183
+ end
184
+ @slowest_transaction_threshold = @slowest_transaction_threshold.to_f
185
+
186
+ if @use_transaction_sampler
187
+ log.info "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
188
+ else
189
+ log.info "Transaction tracing not enabled."
190
+ end
191
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
192
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
193
+ @random_sample = sampler_config.fetch('random_sample', false)
194
+ log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
195
+ # Initialize transaction sampler
196
+ @transaction_sampler.random_sampling = @random_sample
197
+
198
+ if control.monitor_mode?
199
+ if !control.license_key
200
+ @invalid_license = true
201
+ control.log! "No license key found. Please edit your newrelic.yml file and insert your license key.", :error
202
+ elsif control.license_key.length != 40
203
+ @invalid_license = true
204
+ control.log! "Invalid license key: #{control.license_key}", :error
205
+ else
206
+ launch_worker_thread
207
+ # When the VM shuts down, attempt to send a message to the
208
+ # server that this agent run is stopping, assuming it has
209
+ # successfully connected
210
+ # This shutdown handler doesn't work if Sinatra is running
211
+ # because it executes in the shutdown handler!
212
+ at_exit { shutdown } unless [:sinatra, :unicorn].include? NewRelic::Control.instance.dispatcher
213
+ end
214
+ end
215
+ control.log! "New Relic RPM Agent #{NewRelic::VERSION::STRING} Initialized: pid = #{$$}"
216
+ control.log! "Agent Log found in #{NewRelic::Control.instance.log_file}" if NewRelic::Control.instance.log_file
217
+ end
218
+
219
+ private
220
+ def collector
221
+ @collector ||= control.server
222
+ end
223
+
224
+ # Connect to the server, and run the worker loop forever.
225
+ # Will not return.
226
+ def run_task_loop
227
+ # determine the reporting period (server based)
228
+ # note if the agent attempts to report more frequently than
229
+ # the specified report data, then it will be ignored.
230
+
231
+ control.log! "Reporting performance data every #{@report_period} seconds."
232
+ @task_loop.add_task(@report_period, "Timeslice Data Send") do
233
+ harvest_and_send_timeslice_data
234
+ end
235
+
236
+ if @should_send_samples && @use_transaction_sampler
237
+ @task_loop.add_task(@report_period, "Transaction Sampler Send") do
238
+ harvest_and_send_slowest_sample
239
+ end
240
+ elsif !control.developer_mode?
241
+ # We still need the sampler for dev mode.
242
+ @transaction_sampler.disable
243
+ end
244
+
245
+ if @should_send_errors && @error_collector.enabled
246
+ @task_loop.add_task(@report_period, "Error Send") do
247
+ harvest_and_send_errors
248
+ end
249
+ end
250
+ log.debug("Running worker loop")
251
+ @task_loop.run
252
+ rescue StandardError
253
+ log.debug("Error in worker loop: #{$!}")
254
+ @connected = false
255
+ raise
256
+ end
257
+
258
+ def launch_worker_thread
259
+ if (control.dispatcher == :passenger && $0 =~ /ApplicationSpawner/)
260
+ log.debug "Process is passenger spawner - don't connect to RPM service"
261
+ return
262
+ end
263
+
264
+ @task_loop = WorkerLoop.new(log)
265
+
266
+ if control['check_bg_loading']
267
+ log.warn "Agent background loading checking turned on"
268
+ require 'new_relic/agent/patch_const_missing'
269
+ ClassLoadingWatcher.enable_warning
270
+ end
271
+ log.debug "Creating RPM worker thread."
272
+ @worker_thread = Thread.new do
273
+ begin
274
+ ClassLoadingWatcher.background_thread=Thread.current if control['check_bg_loading']
275
+ NewRelic::Agent.disable_all_tracing do
276
+ connect
277
+ run_task_loop if @connected
278
+ end
279
+ rescue NewRelic::Agent::ForceRestartException => e
280
+ log.info e.message
281
+ # disconnect and start over.
282
+ # clear the stats engine
283
+ reset_stats
284
+ @connected = false
285
+ # Wait a short time before trying to reconnect
286
+ sleep 30
287
+ retry
288
+ rescue IgnoreSilentlyException
289
+ control.log! "Unable to establish connection with the server. Run with log level set to debug for more information."
290
+ rescue Exception => e
291
+ @connected = false
292
+ control.log! e, :error
293
+ control.log! e.backtrace.join("\n "), :error
294
+ end
295
+ end
296
+ @worker_thread['newrelic_label'] = 'Worker Loop'
297
+ end
298
+
299
+ def control
300
+ NewRelic::Control.instance
301
+ end
302
+
303
+ def initialize
304
+ @connected = false
305
+ @launch_time = Time.now
306
+
307
+ @metric_ids = {}
308
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
309
+ @stats_engine = NewRelic::Agent::StatsEngine.new
310
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new
311
+ @stats_engine.transaction_sampler = @transaction_sampler
312
+ @error_collector = NewRelic::Agent::ErrorCollector.new
313
+
314
+ @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
315
+
316
+ @invalid_license = false
317
+
318
+ @last_harvest_time = Time.now
319
+ @obfuscator = method(:default_sql_obfuscator)
320
+ end
321
+
322
+ # Connect to the server and validate the license. If successful,
323
+ # @connected has true when finished. If not successful, you can
324
+ # keep calling this. Return false if we could not establish a
325
+ # connection with the server and we should not retry, such as if
326
+ # there's a bad license key.
327
+ def connect
328
+ # wait a few seconds for the web server to boot, necessary in development
329
+ connect_retry_period = 5
330
+ connect_attempts = 0
331
+ @agent_id = nil
332
+ begin
333
+ sleep connect_retry_period.to_i
334
+ environment = control['send_environment_info'] != false ? control.local_env.snapshot : []
335
+ @agent_id ||= invoke_remote :start, @local_host, {
336
+ :pid => $$,
337
+ :launch_time => @launch_time.to_f,
338
+ :agent_version => NewRelic::VERSION::STRING,
339
+ :environment => environment,
340
+ :settings => control.settings,
341
+ :validate_seed => ENV['NR_VALIDATE_SEED'],
342
+ :validate_token => ENV['NR_VALIDATE_TOKEN'] }
343
+
344
+ host = invoke_remote(:get_redirect_host)
345
+
346
+ @collector = control.server_from_host(host) if host
347
+
348
+ @report_period = invoke_remote :get_data_report_period, @agent_id
349
+
350
+ control.log! "Connected to NewRelic Service at #{@collector}"
351
+ log.debug "Agent ID = #{@agent_id}."
352
+
353
+ # Ask the server for permission to send transaction samples.
354
+ # determined by subscription license.
355
+ @should_send_samples = invoke_remote :should_collect_samples, @agent_id
356
+
357
+ if @should_send_samples
358
+ sampling_rate = invoke_remote :sampling_rate, @agent_id if @random_sample
359
+ @transaction_sampler.sampling_rate = sampling_rate
360
+ log.info "Transaction sample rate: #{@transaction_sampler.sampling_rate}" if sampling_rate
361
+ end
362
+
363
+ # Ask for permission to collect error data
364
+ @should_send_errors = invoke_remote :should_collect_errors, @agent_id
365
+
366
+ log.info "Transaction traces will be sent to the RPM service" if @use_transaction_sampler && @should_send_samples
367
+ log.info "Errors will be sent to the RPM service" if @error_collector.enabled && @should_send_errors
368
+
369
+ @connected = true
370
+
371
+ rescue LicenseException => e
372
+ control.log! e.message, :error
373
+ control.log! "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
374
+ @invalid_license = true
375
+ return false
376
+
377
+ rescue Timeout::Error, StandardError => e
378
+ log.info "Unable to establish connection with New Relic RPM Service at #{control.server}"
379
+ unless e.instance_of? IgnoreSilentlyException
380
+ log.error e.message
381
+ log.debug e.backtrace.join("\n")
382
+ end
383
+ # retry logic
384
+ connect_attempts += 1
385
+ case connect_attempts
386
+ when 1..2
387
+ connect_retry_period, period_msg = 60, "1 minute"
388
+ when 3..5 then
389
+ connect_retry_period, period_msg = 60 * 2, "2 minutes"
390
+ else
391
+ connect_retry_period, period_msg = 10*60, "10 minutes"
392
+ end
393
+ log.info "Will re-attempt in #{period_msg}"
394
+ retry
395
+ end
396
+ end
397
+
398
+ def determine_host
399
+ Socket.gethostname
400
+ end
401
+
402
+ def determine_home_directory
403
+ control.root
404
+ end
405
+ def reset_stats
406
+ @stats_engine.reset_stats
407
+ @metric_ids = {}
408
+ @unsent_errors = []
409
+ @traces = nil
410
+ @unsent_timeslice_data = {}
411
+ @last_harvest_time = Time.now
412
+ @launch_time = Time.now
413
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
414
+ end
415
+
416
+ def harvest_and_send_timeslice_data
417
+
418
+ NewRelic::Agent::BusyCalculator.harvest_busy
419
+
420
+ now = Time.now
421
+
422
+ @unsent_timeslice_data ||= {}
423
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
424
+
425
+ begin
426
+ # In this version of the protocol, we get back an assoc array of spec to id.
427
+ metric_ids = invoke_remote(:metric_data, @agent_id,
428
+ @last_harvest_time.to_f,
429
+ now.to_f,
430
+ @unsent_timeslice_data.values)
431
+
432
+ rescue Timeout::Error
433
+ # assume that the data was received. chances are that it was
434
+ metric_ids = nil
435
+ end
436
+
437
+ metric_ids.each do | spec, id |
438
+ @metric_ids[spec] = id
439
+ end if metric_ids
440
+
441
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
442
+
443
+ # if we successfully invoked this web service, then clear the unsent message cache.
444
+ @unsent_timeslice_data = {}
445
+ @last_harvest_time = now
446
+
447
+ # handle_messages
448
+
449
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
450
+ # then the metric data is downsampled for another timeslices
451
+ end
452
+
453
+ def harvest_and_send_slowest_sample
454
+ @traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
455
+
456
+ unless @traces.empty?
457
+ now = Time.now
458
+ log.debug "Sending (#{@traces.length}) transaction traces"
459
+ begin
460
+ # take the traces and prepare them for sending across the
461
+ # wire. This includes gathering SQL explanations, stripping
462
+ # out stack traces, and normalizing SQL. note that we
463
+ # explain only the sql statements whose segments' execution
464
+ # times exceed our threshold (to avoid unnecessary overhead
465
+ # of running explains on fast queries.)
466
+ traces = @traces.collect {|trace| trace.prepare_to_send(:explain_sql => @explain_threshold, :record_sql => @record_sql, :keep_backtraces => true, :explain_enabled => @explain_enabled)}
467
+ invoke_remote :transaction_sample_data, @agent_id, traces
468
+ rescue PostTooBigException
469
+ # we tried to send too much data, drop the first trace and
470
+ # try again
471
+ retry if @traces.shift
472
+ end
473
+
474
+ log.debug "#{now}: sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
475
+ end
476
+
477
+ # if we succeed sending this sample, then we don't need to keep
478
+ # the slowest sample around - it has been sent already and we
479
+ # can collect the next one
480
+ @traces = nil
481
+
482
+ # note - exceptions are logged in invoke_remote. If an
483
+ # exception is encountered here, then the slowest sample of is
484
+ # determined of the entire period since the last reported
485
+ # sample.
486
+ end
487
+
488
+ def harvest_and_send_errors
489
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
490
+ if @unsent_errors && @unsent_errors.length > 0
491
+ log.debug "Sending #{@unsent_errors.length} errors"
492
+ begin
493
+ invoke_remote :error_data, @agent_id, @unsent_errors
494
+ rescue PostTooBigException
495
+ @unsent_errors.shift
496
+ retry
497
+ end
498
+ # if the remote invocation fails, then we never clear
499
+ # @unsent_errors, and therefore we can re-attempt to send on
500
+ # the next heartbeat. Note the error collector maxes out at
501
+ # 20 instances to prevent leakage
502
+ @unsent_errors = []
503
+ end
504
+ end
505
+
506
+ def compress_data(object)
507
+ dump = Marshal.dump(object)
508
+
509
+ # this checks to make sure mongrel won't choke on big uploads
510
+ check_post_size(dump)
511
+
512
+ # we currently optimize for CPU here since we get roughly a 10x
513
+ # reduction in message size with this, and CPU overhead is at a
514
+ # premium. For extra-large posts, we use the higher compression
515
+ # since otherwise it actually errors out.
516
+
517
+ dump_size = dump.size
518
+
519
+ # small payloads don't need compression
520
+ return [dump, 'identity'] if dump_size < 2000
521
+
522
+ # medium payloads get fast compression, to save CPU
523
+ # big payloads get all the compression possible, to stay under
524
+ # the 2,000,000 byte post threshold
525
+ compression = dump_size < 2000000 ? Zlib::BEST_SPEED : Zlib::BEST_COMPRESSION
526
+
527
+ [Zlib::Deflate.deflate(dump, compression), 'deflate']
528
+ end
529
+
530
+ def check_post_size(post_string)
531
+ # TODO: define this as a config option on the server side
532
+ return if post_string.size < control.post_size_limit
533
+ log.warn "Tried to send too much data: #{post_string.size} bytes"
534
+ raise PostTooBigException
535
+ end
536
+
537
+ def send_request(opts)
538
+ request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'ACCEPT-ENCODING' => 'gzip', 'HOST' => opts[:collector].name)
539
+ request.content_type = "application/octet-stream"
540
+ request.body = opts[:data]
541
+
542
+ log.debug "connect to #{opts[:collector]}#{opts[:uri]}"
543
+
544
+ response = nil
545
+ http = control.http_connection(collector)
546
+ begin
547
+ timeout(@request_timeout) do
548
+ response = http.request(request)
549
+ end
550
+ rescue Timeout::Error
551
+ log.warn "Timed out trying to post data to RPM (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
552
+ raise
553
+ end
554
+ if response.is_a? Net::HTTPServiceUnavailable
555
+ log.debug(response.body || response.message)
556
+ raise IgnoreSilentlyException
557
+ elsif response.is_a? Net::HTTPGatewayTimeOut
558
+ log.debug("Timed out getting response: #{response.message}")
559
+ raise Timeout::Error, response.message
560
+ elsif !(response.is_a? Net::HTTPSuccess)
561
+ log.debug "Unexpected response from server: #{response.code}: #{response.message}"
562
+ raise IgnoreSilentlyException
563
+ end
564
+ response
565
+ end
566
+
567
+ def decompress_response(response)
568
+ if response['content-encoding'] != 'gzip'
569
+ log.debug "Uncompressed content returned"
570
+ return response.body
571
+ end
572
+ log.debug "Decompressing return value"
573
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
574
+ i.read
575
+ end
576
+
577
+ def check_for_exception(response)
578
+ dump = decompress_response(response)
579
+ value = Marshal.load(dump)
580
+ raise value if value.is_a? Exception
581
+ value
582
+ end
583
+
584
+ def remote_method_uri(method)
585
+ uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
586
+ uri << "?run_id=#{@agent_id}" if @agent_id
587
+ uri
588
+ end
589
+
590
+ # send a message via post
591
+ def invoke_remote(method, *args)
592
+ #determines whether to zip the data or send plain
593
+ post_data, encoding = compress_data(args)
594
+
595
+ response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
596
+
597
+ # raises the right exception if the remote server tells it to die
598
+ return check_for_exception(response)
599
+ rescue ForceRestartException => e
600
+ log.info e.message
601
+ raise
602
+ rescue ForceDisconnectException => e
603
+ log.error "RPM forced this agent to disconnect (#{e.message})\n" \
604
+ "Restart this process to resume monitoring via rpm.newrelic.com."
605
+ # when a disconnect is requested, stop the current thread, which
606
+ # is the worker thread that gathers data and talks to the
607
+ # server.
608
+ @connected = false
609
+ Thread.exit
610
+ rescue SystemCallError, SocketError => e
611
+ # These include Errno connection errors
612
+ log.debug "Recoverable error connecting to the server: #{e}"
613
+ raise IgnoreSilentlyException
614
+ end
615
+
616
+ def graceful_disconnect
617
+ if @connected && !control.developer_mode?
618
+ begin
619
+ log.debug "Sending graceful shutdown message to #{control.server}"
620
+
621
+ @request_timeout = 10
622
+
623
+ log.debug "Sending RPM service agent run shutdown message"
624
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
625
+
626
+ log.debug "Graceful shutdown complete"
627
+
628
+ rescue Timeout::Error, StandardError
629
+ end
630
+ else
631
+ log.debug "Bypassing graceful shutdown - agent not connected"
632
+ end
633
+ end
634
+ def default_sql_obfuscator(sql)
635
+ sql = sql.dup
636
+ # This is hardly readable. Use the unit tests.
637
+ # remove single quoted strings:
638
+ sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
639
+ # remove double quoted strings:
640
+ sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
641
+ # replace all number literals
642
+ sql.gsub!(/\d+/, "?")
643
+ sql
644
+ end
645
+ end
646
+
647
+ end