onyx_newrelic_rpm 2.12.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. data/CHANGELOG +436 -0
  2. data/LICENSE +37 -0
  3. data/README.md +138 -0
  4. data/bin/mongrel_rpm +33 -0
  5. data/bin/newrelic_cmd +4 -0
  6. data/cert/cacert.pem +34 -0
  7. data/install.rb +46 -0
  8. data/lib/new_relic/agent/agent.rb +668 -0
  9. data/lib/new_relic/agent/busy_calculator.rb +91 -0
  10. data/lib/new_relic/agent/chained_call.rb +13 -0
  11. data/lib/new_relic/agent/error_collector.rb +128 -0
  12. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  13. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +92 -0
  14. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +45 -0
  15. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  16. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +404 -0
  17. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  18. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +22 -0
  19. data/lib/new_relic/agent/instrumentation/memcache.rb +40 -0
  20. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  21. data/lib/new_relic/agent/instrumentation/merb/errors.rb +9 -0
  22. data/lib/new_relic/agent/instrumentation/metric_frame.rb +307 -0
  23. data/lib/new_relic/agent/instrumentation/net.rb +17 -0
  24. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +22 -0
  25. data/lib/new_relic/agent/instrumentation/rack.rb +112 -0
  26. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  27. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  28. data/lib/new_relic/agent/instrumentation/rails/errors.rb +24 -0
  29. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +45 -0
  30. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +21 -0
  31. data/lib/new_relic/agent/instrumentation/sinatra.rb +46 -0
  32. data/lib/new_relic/agent/instrumentation/sunspot.rb +17 -0
  33. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +10 -0
  34. data/lib/new_relic/agent/method_tracer.rb +350 -0
  35. data/lib/new_relic/agent/sampler.rb +46 -0
  36. data/lib/new_relic/agent/samplers/cpu_sampler.rb +54 -0
  37. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +37 -0
  38. data/lib/new_relic/agent/samplers/memory_sampler.rb +142 -0
  39. data/lib/new_relic/agent/samplers/object_sampler.rb +24 -0
  40. data/lib/new_relic/agent/shim_agent.rb +21 -0
  41. data/lib/new_relic/agent/stats_engine/metric_stats.rb +118 -0
  42. data/lib/new_relic/agent/stats_engine/samplers.rb +80 -0
  43. data/lib/new_relic/agent/stats_engine/transactions.rb +149 -0
  44. data/lib/new_relic/agent/stats_engine.rb +24 -0
  45. data/lib/new_relic/agent/transaction_sampler.rb +315 -0
  46. data/lib/new_relic/agent/worker_loop.rb +80 -0
  47. data/lib/new_relic/agent.rb +369 -0
  48. data/lib/new_relic/collection_helper.rb +69 -0
  49. data/lib/new_relic/commands/deployments.rb +145 -0
  50. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  51. data/lib/new_relic/control/external.rb +13 -0
  52. data/lib/new_relic/control/merb.rb +24 -0
  53. data/lib/new_relic/control/rails.rb +151 -0
  54. data/lib/new_relic/control/rails3.rb +75 -0
  55. data/lib/new_relic/control/ruby.rb +36 -0
  56. data/lib/new_relic/control/sinatra.rb +18 -0
  57. data/lib/new_relic/control.rb +528 -0
  58. data/lib/new_relic/delayed_job_injection.rb +27 -0
  59. data/lib/new_relic/histogram.rb +89 -0
  60. data/lib/new_relic/local_environment.rb +333 -0
  61. data/lib/new_relic/merbtasks.rb +6 -0
  62. data/lib/new_relic/metric_data.rb +42 -0
  63. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  64. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  65. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  66. data/lib/new_relic/metric_parser/controller.rb +54 -0
  67. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  68. data/lib/new_relic/metric_parser/errors.rb +6 -0
  69. data/lib/new_relic/metric_parser/external.rb +50 -0
  70. data/lib/new_relic/metric_parser/mem_cache.rb +50 -0
  71. data/lib/new_relic/metric_parser/other_transaction.rb +15 -0
  72. data/lib/new_relic/metric_parser/view.rb +61 -0
  73. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  74. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  75. data/lib/new_relic/metric_parser.rb +125 -0
  76. data/lib/new_relic/metric_spec.rb +67 -0
  77. data/lib/new_relic/metrics.rb +9 -0
  78. data/lib/new_relic/noticed_error.rb +24 -0
  79. data/lib/new_relic/rack/metric_app.rb +58 -0
  80. data/lib/new_relic/rack/mongrel_rpm.ru +25 -0
  81. data/lib/new_relic/rack/newrelic.yml +26 -0
  82. data/lib/new_relic/rack_app.rb +5 -0
  83. data/lib/new_relic/recipes.rb +82 -0
  84. data/lib/new_relic/stats.rb +376 -0
  85. data/lib/new_relic/transaction_analysis.rb +124 -0
  86. data/lib/new_relic/transaction_sample.rb +654 -0
  87. data/lib/new_relic/version.rb +55 -0
  88. data/lib/new_relic_api.rb +276 -0
  89. data/lib/newrelic_rpm.rb +40 -0
  90. data/lib/tasks/all.rb +4 -0
  91. data/lib/tasks/install.rake +7 -0
  92. data/lib/tasks/tests.rake +15 -0
  93. data/newrelic.yml +235 -0
  94. data/onyx_newrelic_rpm.gemspec +221 -0
  95. data/recipes/newrelic.rb +6 -0
  96. data/test/active_record_fixtures.rb +55 -0
  97. data/test/config/newrelic.yml +43 -0
  98. data/test/config/test_control.rb +38 -0
  99. data/test/new_relic/agent/active_record_instrumentation_test.rb +287 -0
  100. data/test/new_relic/agent/agent_controller_test.rb +280 -0
  101. data/test/new_relic/agent/agent_test_controller.rb +82 -0
  102. data/test/new_relic/agent/busy_calculator_test.rb +79 -0
  103. data/test/new_relic/agent/collection_helper_test.rb +125 -0
  104. data/test/new_relic/agent/error_collector_test.rb +171 -0
  105. data/test/new_relic/agent/memcache_instrumentation_test.rb +103 -0
  106. data/test/new_relic/agent/method_tracer_test.rb +340 -0
  107. data/test/new_relic/agent/metric_data_test.rb +56 -0
  108. data/test/new_relic/agent/metric_frame_test.rb +51 -0
  109. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  110. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  111. data/test/new_relic/agent/net_instrumentation_test.rb +77 -0
  112. data/test/new_relic/agent/rpm_agent_test.rb +138 -0
  113. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  114. data/test/new_relic/agent/stats_engine/samplers_test.rb +72 -0
  115. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +184 -0
  116. data/test/new_relic/agent/task_instrumentation_test.rb +193 -0
  117. data/test/new_relic/agent/testable_agent.rb +13 -0
  118. data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
  119. data/test/new_relic/agent/transaction_sample_test.rb +186 -0
  120. data/test/new_relic/agent/transaction_sampler_test.rb +385 -0
  121. data/test/new_relic/agent/worker_loop_test.rb +60 -0
  122. data/test/new_relic/control_test.rb +117 -0
  123. data/test/new_relic/deployments_api_test.rb +69 -0
  124. data/test/new_relic/environment_test.rb +75 -0
  125. data/test/new_relic/metric_parser_test.rb +172 -0
  126. data/test/new_relic/metric_spec_test.rb +177 -0
  127. data/test/new_relic/shim_agent_test.rb +9 -0
  128. data/test/new_relic/stats_test.rb +311 -0
  129. data/test/new_relic/version_number_test.rb +89 -0
  130. data/test/test_helper.rb +53 -0
  131. data/test/ui/newrelic_controller_test.rb +14 -0
  132. data/test/ui/newrelic_helper_test.rb +53 -0
  133. data/ui/controllers/newrelic_controller.rb +220 -0
  134. data/ui/helpers/google_pie_chart.rb +49 -0
  135. data/ui/helpers/newrelic_helper.rb +319 -0
  136. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  137. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  138. data/ui/views/newrelic/_sample.rhtml +19 -0
  139. data/ui/views/newrelic/_segment.rhtml +28 -0
  140. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  141. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  142. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  143. data/ui/views/newrelic/_show_sample_sql.rhtml +20 -0
  144. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  145. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  146. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  147. data/ui/views/newrelic/_table.rhtml +12 -0
  148. data/ui/views/newrelic/explain_sql.rhtml +42 -0
  149. data/ui/views/newrelic/images/arrow-close.png +0 -0
  150. data/ui/views/newrelic/images/arrow-open.png +0 -0
  151. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  152. data/ui/views/newrelic/images/file_icon.png +0 -0
  153. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  154. data/ui/views/newrelic/images/new-relic-rpm-desktop.gif +0 -0
  155. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  156. data/ui/views/newrelic/images/textmate.png +0 -0
  157. data/ui/views/newrelic/index.rhtml +57 -0
  158. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  159. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  160. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  161. data/ui/views/newrelic/show_sample.rhtml +80 -0
  162. data/ui/views/newrelic/show_source.rhtml +3 -0
  163. data/ui/views/newrelic/stylesheets/style.css +484 -0
  164. data/ui/views/newrelic/threads.rhtml +52 -0
  165. metadata +248 -0
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ New Relic RPM
2
+ =============
3
+
4
+ New Relic RPM is a Ruby performance management system, developed by
5
+ [New Relic, Inc](http://www.newrelic.com). RPM provides you with deep
6
+ information about the performance of your Ruby on Rails or Merb
7
+ application as it runs in production. The New Relic Agent is
8
+ dual-purposed as a either a Rails plugin or a Gem, hosted on
9
+ [github](http://github.com/newrelic/rpm/tree/master) and Rubyforge.
10
+
11
+ The New Relic Agent runs in one of two modes:
12
+
13
+ **Developer Mode** : Adds a web interface mapped to /newrelic to your
14
+ application for showing detailed performance metrics on a page by
15
+ page basis.
16
+
17
+ **Production Mode** : Low overhead instrumentation that captures
18
+ detailed information on your application running in production and
19
+ transmits them to rpm.newrelic.com where you can monitor them in
20
+ real time.
21
+
22
+ ### Supported Environments
23
+
24
+ * Ruby 1.8.6, 1.8.7 or 1.9.1
25
+ * JRuby
26
+ * Rails 1.2.6 or above
27
+ * Merb 1.0 or above
28
+
29
+ Developer Mode
30
+ --------------
31
+
32
+ Developer mode is on by default when you run your application in the
33
+ development environment (but not when it runs in other environments.)
34
+ When running in developer mode, RPM will track the performance of
35
+ every http request serviced by your application, and store in memory
36
+ this information for the last 100 http transactions.
37
+
38
+ When running in Developer Mode, the RPM will also add a few pages to
39
+ your application that allow you to analyze this performance
40
+ information. (Don't worry--those pages are not added to your
41
+ application's routes when you run in production mode.)
42
+
43
+ To view this performance information, including detailed SQL statement
44
+ analysis, open `/newrelic` in your web application. For instance if
45
+ you are running mongrel or thin on port 3000, enter the following into
46
+ your browser:
47
+
48
+ http://localhost:3000/newrelic
49
+
50
+ Production Mode
51
+ ---------------
52
+
53
+ When your application runs in the production environment, the New
54
+ Relic agent runs in production mode. It connects to the New Relic RPM
55
+ service and sends deep performance data to the RPM service for your
56
+ analysis. To view this data, login to
57
+ [http://rpm.newrelic.com](http://rpm.newrelic.com).
58
+
59
+ NOTE: You must have a valid account and license key to view this data
60
+ online. Refer to instructions in *Getting Started*, below.
61
+
62
+ Getting Started
63
+ ===============
64
+
65
+ RPM requires an agent be installed in the application as either a
66
+ Rails plug-in or a gem. Both are available on RubyForge--instructions
67
+ below.
68
+
69
+ To use Developer Mode, simply install the gem or plugin into your
70
+ application and follow the instructions below.
71
+
72
+ To monitor your applications in production, create an account at
73
+ [www.newrelic.com](http://newrelic.com/get-RPM.html). There you can
74
+ sign up for a free Lite account or one of our paid subscriptions.
75
+
76
+ Once you receive the welcome e-mail with a license key and
77
+ `newrelic.yml` file, copy the `newrelic.yml` file into your app config
78
+ directory.
79
+
80
+ ### Rails Plug-In Installation
81
+
82
+ script/plugin install http://newrelic.rubyforge.org/svn/newrelic_rpm
83
+
84
+ ### Gem Installation
85
+
86
+ sudo gem install newrelic_rpm
87
+
88
+ For Rails, edit `environment.rb` and add to the initalizer block:
89
+
90
+ config.gem "newrelic_rpm"
91
+
92
+ The Developer Mode is unavailable when using the gem on Rails versions
93
+ prior to 2.0.
94
+
95
+ ### Merb Support
96
+
97
+ To monitor a merb app install the newrelic_rpm gem and add
98
+
99
+ dependency 'newrelic_rpm'
100
+
101
+ to your init.rb file.
102
+
103
+ Current features implemented:
104
+
105
+ * Standard monitoring, overview pages
106
+ * Error capturing
107
+ * Full Active Record instrumentation, including SQL explains
108
+ * Very limited Data Mapper instrumentation
109
+ * Transaction Traces are implemented but will not be very useful
110
+ with Data Mapper until more work is done with the Data Mapper
111
+ instrumentation
112
+
113
+ Still under development:
114
+
115
+ * Developer Mode
116
+ * Data Mapper bindings
117
+
118
+ ### Github
119
+
120
+ The agent is also available on Github under newrelic/rpm. Fork away!
121
+
122
+ ### Support
123
+
124
+ Reach out to us--and to fellow RPM users--at
125
+ [support.newrelic.com](http://support.newrelic.com/discussions/support).
126
+ There you'll find documentation, FAQs, and forums where you can submit
127
+ suggestions and discuss RPM with New Relic staff and other users.
128
+
129
+ Find a bug? E-mail support@newrelic.com, or post it to
130
+ [support.newrelic.com](http://support.newrelic.com/discussions/support).
131
+
132
+ Refer to [our website](http://www.newrelic.com/support) for other
133
+ support channels.
134
+
135
+ Thank you, and may your application scale to infinity plus one.
136
+
137
+ Lew Cirne, Founder and CEO<br/>
138
+ New Relic, Inc.
data/bin/mongrel_rpm ADDED
@@ -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
+
data/bin/newrelic_cmd ADDED
@@ -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'
data/cert/cacert.pem ADDED
@@ -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
+
data/install.rb ADDED
@@ -0,0 +1,46 @@
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
+
41
+ end
42
+ end
43
+
44
+ if __FILE__ == $0 || $0 =~ /script\/plugin/
45
+ install_newrelic_config_file
46
+ end
@@ -0,0 +1,668 @@
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
12
+ module Agent
13
+
14
+ # The Agent is a singleton that is instantiated when the plugin is
15
+ # activated.
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
+ attr_reader :obfuscator
32
+ attr_reader :stats_engine
33
+ attr_reader :transaction_sampler
34
+ attr_reader :error_collector
35
+ attr_reader :record_sql
36
+ attr_reader :histogram
37
+ attr_reader :metric_ids
38
+
39
+ # Should only be called by NewRelic::Control
40
+ def self.instance
41
+ @instance ||= self.new
42
+ end
43
+ # This method is deprecated. Use NewRelic::Agent.manual_start
44
+ def manual_start(ignored=nil, also_ignored=nil)
45
+ raise "This method no longer supported. Instead use the class method NewRelic::Agent.manual_start"
46
+ end
47
+
48
+ # This method should be called in a forked process after a fork.
49
+ # It assumes the parent process initialized the agent, but does
50
+ # not assume the agent started.
51
+ #
52
+ # * It clears any metrics carried over from the parent process
53
+ # * Restarts the sampler thread if necessary
54
+ # * Initiates a new agent run and worker loop unless that was done
55
+ # in the parent process and +:force_reconnect+ is not true
56
+ #
57
+ # Options:
58
+ # * <tt>:force_reconnect => true</tt> to force the spawned process to
59
+ # establish a new connection, such as when forking a long running process.
60
+ # The default is false--it will only connect to the server if the parent
61
+ # had not connected.
62
+ # * <tt>:keep_retrying => false</tt> if we try to initiate a new
63
+ # connection, this tells me to only try it once so this method returns
64
+ # quickly if there is some kind of latency with the server.
65
+ def after_fork(options={})
66
+
67
+ # @connected gets false after we fail to connect or have an error
68
+ # connecting. @connected has nil if we haven't finished trying to connect.
69
+ # or we didn't attempt a connection because this is the master process
70
+
71
+ log.debug "Agent received after_fork notice in #$$: [#{control.agent_enabled?}; monitor=#{control.monitor_mode?}; connected: #{@connected.inspect}; thread=#{@worker_thread.inspect}]"
72
+ return if !control.agent_enabled? or
73
+ !control.monitor_mode? or
74
+ @connected == false or
75
+ @worker_thread && @worker_thread.alive?
76
+
77
+ log.info "Starting the worker thread in #$$ after forking."
78
+
79
+ # Clear out stats that are left over from parent process
80
+ reset_stats
81
+
82
+ start_worker_thread(options)
83
+ @stats_engine.start_sampler_thread
84
+ end
85
+
86
+ # True if we have initialized and completed 'start'
87
+ def started?
88
+ @started
89
+ end
90
+
91
+ # Attempt a graceful shutdown of the agent.
92
+ def shutdown
93
+ return if not started?
94
+ if @worker_loop
95
+ @worker_loop.stop
96
+
97
+ log.debug "Starting Agent shutdown"
98
+
99
+ # if litespeed, then ignore all future SIGUSR1 - it's
100
+ # litespeed trying to shut us down
101
+
102
+ if control.dispatcher == :litespeed
103
+ Signal.trap("SIGUSR1", "IGNORE")
104
+ Signal.trap("SIGTERM", "IGNORE")
105
+ end
106
+
107
+ begin
108
+ graceful_disconnect
109
+ rescue => e
110
+ log.error e
111
+ log.error e.backtrace.join("\n")
112
+ end
113
+ end
114
+ @started = nil
115
+ end
116
+
117
+ def start_transaction
118
+ @stats_engine.start_transaction
119
+ end
120
+
121
+ def end_transaction
122
+ @stats_engine.end_transaction
123
+ end
124
+
125
+ def set_record_sql(should_record)
126
+ prev = Thread::current[:record_sql]
127
+ Thread::current[:record_sql] = should_record
128
+ prev.nil? || prev
129
+ end
130
+
131
+ def set_record_tt(should_record)
132
+ prev = Thread::current[:record_tt]
133
+ Thread::current[:record_tt] = should_record
134
+ prev.nil? || prev
135
+ end
136
+ # Push flag indicating whether we should be tracing in this
137
+ # thread.
138
+ def push_trace_execution_flag(should_trace=false)
139
+ (Thread.current[:newrelic_untraced] ||= []) << should_trace
140
+ end
141
+
142
+ # Pop the current trace execution status. Restore trace execution status
143
+ # to what it was before we pushed the current flag.
144
+ def pop_trace_execution_flag
145
+ Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
146
+ end
147
+
148
+ def set_sql_obfuscator(type, &block)
149
+ if type == :before
150
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
151
+ elsif type == :after
152
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
153
+ elsif type == :replace
154
+ @obfuscator = block
155
+ else
156
+ fail "unknown sql_obfuscator type #{type}"
157
+ end
158
+ end
159
+
160
+ def log
161
+ NewRelic::Agent.logger
162
+ end
163
+
164
+ # Start up the agent. This verifies that the agent_enabled? is
165
+ # true and initializes the sampler based on the current
166
+ # configuration settings. Then it will fire up the background
167
+ # thread for sending data to the server if applicable.
168
+ def start
169
+ if started?
170
+ control.log! "Agent Started Already!", :error
171
+ return
172
+ end
173
+ return if !control.agent_enabled?
174
+ @started = true
175
+ @local_host = determine_host
176
+
177
+ if control.dispatcher.nil? || control.dispatcher.to_s.empty?
178
+ log.info "No dispatcher detected."
179
+ else
180
+ log.info "Dispatcher: #{control.dispatcher.to_s}"
181
+ end
182
+ log.info "Application: #{control.app_names.join(", ")}" unless control.app_names.empty?
183
+
184
+ sampler_config = control.fetch('transaction_tracer', {})
185
+ @should_send_samples = sampler_config.fetch('enabled', true)
186
+ log.info "Transaction tracing not enabled." if not @should_send_samples
187
+
188
+ @record_sql = sampler_config.fetch('record_sql', :obfuscated).to_sym
189
+
190
+ # use transaction_threshold: 4.0 to force the TT collection
191
+ # threshold to 4 seconds
192
+ # use transaction_threshold: apdex_f to use your apdex t value
193
+ # multiplied by 4
194
+ # undefined transaction_threshold defaults to 2.0
195
+ apdex_f = 4 * NewRelic::Control.instance.apdex_t
196
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0)
197
+ if @slowest_transaction_threshold =~ /apdex_f/i
198
+ @slowest_transaction_threshold = apdex_f
199
+ end
200
+ @slowest_transaction_threshold = @slowest_transaction_threshold.to_f
201
+
202
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
203
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
204
+ @random_sample = sampler_config.fetch('random_sample', false)
205
+ log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
206
+ # Initialize transaction sampler
207
+ @transaction_sampler.random_sampling = @random_sample
208
+
209
+ case
210
+ when !control.monitor_mode?
211
+ log.warn "Agent configured not to send data in this environment - edit newrelic.yml to change this"
212
+ when !control.license_key
213
+ log.error "No license key found. Please edit your newrelic.yml file and insert your license key."
214
+ when control.license_key.length != 40
215
+ log.error "Invalid license key: #{control.license_key}"
216
+ when [:passenger, :unicorn].include?(control.dispatcher)
217
+ log.info "Connecting workers after forking."
218
+ else
219
+ # Do the connect in the foreground if we are in sync mode
220
+ NewRelic::Agent.disable_all_tracing { connect(:keep_retrying => false) } if control.sync_startup
221
+
222
+ # Start the event loop and initiate connection if necessary
223
+ start_worker_thread
224
+
225
+ # Our shutdown handler needs to run after other shutdown handlers
226
+ # that may be doing things like running the app (hello sinatra).
227
+ if RUBY_VERSION =~ /rubinius/i
228
+ list = at_exit { shutdown }
229
+ # move the shutdown handler to the front of the list, to
230
+ # execute last:
231
+ list.unshift(list.pop)
232
+ elsif !defined?(JRuby) or !defined?(Sinatra::Application)
233
+ at_exit { at_exit { shutdown } }
234
+ end
235
+ end
236
+ control.log! "New Relic RPM Agent #{NewRelic::VERSION::STRING} Initialized: pid = #$$"
237
+ control.log! "Agent Log found in #{NewRelic::Control.instance.log_file}" if NewRelic::Control.instance.log_file
238
+ end
239
+
240
+ # Clear out the metric data, errors, and transaction traces. Reset the histogram data.
241
+ def reset_stats
242
+ @stats_engine.reset_stats
243
+ @unsent_errors = []
244
+ @traces = nil
245
+ @unsent_timeslice_data = {}
246
+ @last_harvest_time = Time.now
247
+ @launch_time = Time.now
248
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
249
+ end
250
+
251
+ private
252
+ def collector
253
+ @collector ||= control.server
254
+ end
255
+
256
+ # Try to launch the worker thread and connect to the server.
257
+ #
258
+ # See #connect for a description of connection_options.
259
+ def start_worker_thread(connection_options = {})
260
+ log.debug "Creating RPM worker thread."
261
+ @worker_thread = Thread.new do
262
+ begin
263
+ NewRelic::Agent.disable_all_tracing do
264
+ # We try to connect. If this returns false that means
265
+ # the server rejected us for a licensing reason and we should
266
+ # just exit the thread. If it returns nil
267
+ # that means it didn't try to connect because we're in the master.
268
+ connect(connection_options)
269
+ if @connected
270
+ # disable transaction sampling if disabled by the server and we're not in dev mode
271
+ if !control.developer_mode? && !@should_send_samples
272
+ @transaction_sampler.disable
273
+ end
274
+ log.info "Reporting performance data every #{@report_period} seconds."
275
+ log.debug "Running worker loop"
276
+ # note if the agent attempts to report more frequently than allowed by the server
277
+ # the server will start dropping data.
278
+ @worker_loop = WorkerLoop.new
279
+ @worker_loop.run(@report_period) do
280
+ harvest_and_send_timeslice_data
281
+ harvest_and_send_slowest_sample if @should_send_samples
282
+ harvest_and_send_errors if error_collector.enabled
283
+ end
284
+ else
285
+ log.debug "No connection. Worker thread finished."
286
+ end
287
+ end
288
+ rescue NewRelic::Agent::ForceRestartException => e
289
+ log.info e.message
290
+ # disconnect and start over.
291
+ # clear the stats engine
292
+ reset_stats
293
+ @metric_ids = {}
294
+ @connected = nil
295
+ # Wait a short time before trying to reconnect
296
+ sleep 30
297
+ retry
298
+ rescue NewRelic::Agent::ForceDisconnectException => e
299
+ # when a disconnect is requested, stop the current thread, which
300
+ # is the worker thread that gathers data and talks to the
301
+ # server.
302
+ log.error "RPM forced this agent to disconnect (#{e.message})"
303
+ @connected = false
304
+ rescue NewRelic::Agent::ServerConnectionException => e
305
+ control.log! "Unable to establish connection with the server. Run with log level set to debug for more information."
306
+ log.debug("#{e.class.name}: #{e.message}\n#{e.backtrace.first}")
307
+ @connected = false
308
+ rescue Exception => e
309
+ log.error "Terminating worker loop: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
310
+ @connected = false
311
+ end # begin
312
+ end # thread new
313
+ @worker_thread['newrelic_label'] = 'Worker Loop'
314
+ end
315
+
316
+ def control
317
+ NewRelic::Control.instance
318
+ end
319
+
320
+ def initialize
321
+ @launch_time = Time.now
322
+
323
+ @metric_ids = {}
324
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
325
+ @stats_engine = NewRelic::Agent::StatsEngine.new
326
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new
327
+ @stats_engine.transaction_sampler = @transaction_sampler
328
+ @error_collector = NewRelic::Agent::ErrorCollector.new
329
+
330
+ @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
331
+
332
+ @last_harvest_time = Time.now
333
+ @obfuscator = method(:default_sql_obfuscator)
334
+ end
335
+
336
+ # Connect to the server and validate the license. If successful,
337
+ # @connected has true when finished. If not successful, you can
338
+ # keep calling this. Return false if we could not establish a
339
+ # connection with the server and we should not retry, such as if
340
+ # there's a bad license key.
341
+ #
342
+ # Set keep_retrying=false to disable retrying and return asap, such as when
343
+ # invoked in the foreground. Otherwise this runs until a successful
344
+ # connection is made, or the server rejects us.
345
+ #
346
+ # * <tt>:keep_retrying => false</tt> to only try to connect once, and
347
+ # return with the connection set to nil. This ensures we may try again
348
+ # later (default true).
349
+ # * <tt>force_reconnect => true</tt> if you want to establish a new connection
350
+ # to the server before running the worker loop. This means you get a separate
351
+ # agent run and RPM sees it as a separate instance (default is false).
352
+ def connect(options)
353
+ # Don't proceed if we already connected (@connected=true) or if we tried
354
+ # to connect and were rejected with prejudice because of a license issue
355
+ # (@connected=false).
356
+ return if !@connected.nil? && !options[:force_reconnect]
357
+
358
+ keep_retrying = options[:keep_retrying].nil? || options[:keep_retrying]
359
+
360
+ # wait a few seconds for the web server to boot, necessary in development
361
+ connect_retry_period = keep_retrying ? 10 : 0
362
+ connect_attempts = 0
363
+ @agent_id = nil
364
+ begin
365
+ sleep connect_retry_period.to_i
366
+ environment = control['send_environment_info'] != false ? control.local_env.snapshot : []
367
+ log.debug "Connecting with validation seed/token: #{control.validate_seed}/#{control.validate_token}" if control.validate_seed
368
+ @agent_id ||= invoke_remote :start, @local_host, {
369
+ :pid => $$,
370
+ :launch_time => @launch_time.to_f,
371
+ :agent_version => NewRelic::VERSION::STRING,
372
+ :environment => environment,
373
+ :settings => control.settings,
374
+ :validate_seed => control.validate_seed,
375
+ :validate_token => control.validate_token }
376
+
377
+ host = invoke_remote(:get_redirect_host)
378
+
379
+ @collector = control.server_from_host(host) if host
380
+
381
+ @report_period = invoke_remote :get_data_report_period, @agent_id
382
+
383
+ control.log! "Connected to NewRelic Service at #{@collector}"
384
+ log.debug "Agent ID = #{@agent_id}."
385
+
386
+ # Ask the server for permission to send transaction samples.
387
+ # determined by subscription license.
388
+ @should_send_samples &&= invoke_remote :should_collect_samples, @agent_id
389
+
390
+ if @should_send_samples
391
+ sampling_rate = invoke_remote :sampling_rate, @agent_id if @random_sample
392
+ @transaction_sampler.sampling_rate = sampling_rate
393
+ log.info "Transaction sample rate: #{@transaction_sampler.sampling_rate}" if sampling_rate
394
+ log.info "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
395
+ end
396
+
397
+ # Ask for permission to collect error data
398
+ error_collector.enabled &&= invoke_remote(:should_collect_errors, @agent_id)
399
+
400
+ log.info "Transaction traces will be sent to the RPM service." if @should_send_samples
401
+ log.info "Errors will be sent to the RPM service." if error_collector.enabled
402
+
403
+ @connected_pid = $$
404
+ @connected = true
405
+
406
+ rescue NewRelic::Agent::LicenseException => e
407
+ control.log! e.message, :error
408
+ control.log! "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
409
+ @connected = false
410
+
411
+ rescue Timeout::Error, StandardError => e
412
+ log.info "Unable to establish connection with New Relic RPM Service at #{control.server}"
413
+ unless e.instance_of? NewRelic::Agent::ServerConnectionException
414
+ log.error e.message
415
+ log.debug e.backtrace.join("\n")
416
+ end
417
+ # retry logic
418
+ if keep_retrying
419
+ connect_attempts += 1
420
+ case connect_attempts
421
+ when 1..2
422
+ connect_retry_period, period_msg = 60, "1 minute"
423
+ when 3..5
424
+ connect_retry_period, period_msg = 60 * 2, "2 minutes"
425
+ else
426
+ connect_retry_period, period_msg = 5 * 60, "5 minutes"
427
+ end
428
+ log.info "Will re-attempt in #{period_msg}"
429
+ retry
430
+ else
431
+ @connected = nil
432
+ end
433
+ end
434
+ end
435
+
436
+ def determine_host
437
+ Socket.gethostname
438
+ end
439
+
440
+ def determine_home_directory
441
+ control.root
442
+ end
443
+
444
+ def harvest_and_send_timeslice_data
445
+
446
+ NewRelic::Agent::BusyCalculator.harvest_busy
447
+
448
+ now = Time.now
449
+
450
+ @unsent_timeslice_data ||= {}
451
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
452
+
453
+ begin
454
+ # In this version of the protocol, we get back an assoc array of spec to id.
455
+ metric_ids = invoke_remote(:metric_data, @agent_id,
456
+ @last_harvest_time.to_f,
457
+ now.to_f,
458
+ @unsent_timeslice_data.values)
459
+
460
+ rescue Timeout::Error
461
+ # assume that the data was received. chances are that it was
462
+ metric_ids = nil
463
+ end
464
+
465
+ metric_ids.each do | spec, id |
466
+ @metric_ids[spec] = id
467
+ end if metric_ids
468
+
469
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
470
+
471
+ # if we successfully invoked this web service, then clear the unsent message cache.
472
+ @unsent_timeslice_data = {}
473
+ @last_harvest_time = now
474
+
475
+ # handle_messages
476
+
477
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
478
+ # then the metric data is downsampled for another timeslices
479
+ end
480
+
481
+ def harvest_and_send_slowest_sample
482
+ @traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
483
+
484
+ unless @traces.empty?
485
+ now = Time.now
486
+ log.debug "Sending (#{@traces.length}) transaction traces"
487
+ begin
488
+ # take the traces and prepare them for sending across the
489
+ # wire. This includes gathering SQL explanations, stripping
490
+ # out stack traces, and normalizing SQL. note that we
491
+ # explain only the sql statements whose segments' execution
492
+ # times exceed our threshold (to avoid unnecessary overhead
493
+ # of running explains on fast queries.)
494
+ traces = @traces.collect {|trace| trace.prepare_to_send(:explain_sql => @explain_threshold, :record_sql => @record_sql, :keep_backtraces => true, :explain_enabled => @explain_enabled)}
495
+ invoke_remote :transaction_sample_data, @agent_id, traces
496
+ rescue PostTooBigException
497
+ # we tried to send too much data, drop the first trace and
498
+ # try again
499
+ retry if @traces.shift
500
+ end
501
+
502
+ log.debug "Sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
503
+ end
504
+
505
+ # if we succeed sending this sample, then we don't need to keep
506
+ # the slowest sample around - it has been sent already and we
507
+ # can collect the next one
508
+ @traces = nil
509
+
510
+ # note - exceptions are logged in invoke_remote. If an
511
+ # exception is encountered here, then the slowest sample of is
512
+ # determined of the entire period since the last reported
513
+ # sample.
514
+ end
515
+
516
+ def harvest_and_send_errors
517
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
518
+ if @unsent_errors && @unsent_errors.length > 0
519
+ log.debug "Sending #{@unsent_errors.length} errors"
520
+ begin
521
+ invoke_remote :error_data, @agent_id, @unsent_errors
522
+ rescue PostTooBigException
523
+ @unsent_errors.shift
524
+ retry
525
+ end
526
+ # if the remote invocation fails, then we never clear
527
+ # @unsent_errors, and therefore we can re-attempt to send on
528
+ # the next heartbeat. Note the error collector maxes out at
529
+ # 20 instances to prevent leakage
530
+ @unsent_errors = []
531
+ end
532
+ end
533
+
534
+ def compress_data(object)
535
+ dump = Marshal.dump(object)
536
+
537
+ # this checks to make sure mongrel won't choke on big uploads
538
+ check_post_size(dump)
539
+
540
+ # we currently optimize for CPU here since we get roughly a 10x
541
+ # reduction in message size with this, and CPU overhead is at a
542
+ # premium. For extra-large posts, we use the higher compression
543
+ # since otherwise it actually errors out.
544
+
545
+ dump_size = dump.size
546
+
547
+ # small payloads don't need compression
548
+ return [dump, 'identity'] if dump_size < (64*1024)
549
+
550
+ # medium payloads get fast compression, to save CPU
551
+ # big payloads get all the compression possible, to stay under
552
+ # the 2,000,000 byte post threshold
553
+ compression = dump_size < 2000000 ? Zlib::BEST_SPEED : Zlib::BEST_COMPRESSION
554
+
555
+ [Zlib::Deflate.deflate(dump, compression), 'deflate']
556
+ end
557
+
558
+ def check_post_size(post_string)
559
+ # TODO: define this as a config option on the server side
560
+ return if post_string.size < control.post_size_limit
561
+ log.warn "Tried to send too much data: #{post_string.size} bytes"
562
+ raise PostTooBigException
563
+ end
564
+
565
+ def send_request(opts)
566
+ request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'HOST' => opts[:collector].name)
567
+ request.content_type = "application/octet-stream"
568
+ request.body = opts[:data]
569
+
570
+ log.debug "Connect to #{opts[:collector]}#{opts[:uri]}"
571
+
572
+ response = nil
573
+ http = control.http_connection(collector)
574
+ begin
575
+ timeout(@request_timeout) do
576
+ response = http.request(request)
577
+ end
578
+ rescue Timeout::Error
579
+ log.warn "Timed out trying to post data to RPM (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
580
+ raise
581
+ end
582
+ if response.is_a? Net::HTTPServiceUnavailable
583
+ raise NewRelic::Agent::ServerConnectionException, "Service unavailable: #{response.body || response.message}"
584
+ elsif response.is_a? Net::HTTPGatewayTimeOut
585
+ log.debug("Timed out getting response: #{response.message}")
586
+ raise Timeout::Error, response.message
587
+ elsif !(response.is_a? Net::HTTPSuccess)
588
+ raise NewRelic::Agent::ServerConnectionException, "Unexpected response from server: #{response.code}: #{response.message}"
589
+ end
590
+ response
591
+ end
592
+
593
+ def decompress_response(response)
594
+ if response['content-encoding'] != 'gzip'
595
+ log.debug "Uncompressed content returned"
596
+ return response.body
597
+ end
598
+ log.debug "Decompressing return value"
599
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
600
+ i.read
601
+ end
602
+
603
+ def check_for_exception(response)
604
+ dump = decompress_response(response)
605
+ value = Marshal.load(dump)
606
+ raise value if value.is_a? Exception
607
+ value
608
+ end
609
+
610
+ def remote_method_uri(method)
611
+ uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
612
+ uri << "?run_id=#{@agent_id}" if @agent_id
613
+ uri
614
+ end
615
+
616
+ # send a message via post
617
+ def invoke_remote(method, *args)
618
+ #determines whether to zip the data or send plain
619
+ post_data, encoding = compress_data(args)
620
+
621
+ response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
622
+
623
+ # raises the right exception if the remote server tells it to die
624
+ return check_for_exception(response)
625
+ rescue NewRelic::Agent::ForceRestartException => e
626
+ log.info e.message
627
+ raise
628
+ rescue SystemCallError, SocketError => e
629
+ # These include Errno connection errors
630
+ raise NewRelic::Agent::ServerConnectionException, "Recoverable error connecting to the server: #{e}"
631
+ end
632
+
633
+ def graceful_disconnect
634
+ if @connected
635
+ begin
636
+ log.debug "Sending graceful shutdown message to #{control.server}"
637
+
638
+ @request_timeout = 10
639
+ log.debug "Flushing unsent metric data to server"
640
+ @worker_loop.run_task
641
+ if @connected_pid == $$
642
+ log.debug "Sending RPM service agent run shutdown message"
643
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
644
+ else
645
+ log.debug "This agent connected from #{@connected_pid}--not sending shutdown"
646
+ end
647
+ log.debug "Graceful disconnect complete"
648
+ rescue Timeout::Error, StandardError
649
+ end
650
+ else
651
+ log.debug "Bypassing graceful disconnect - agent not connected"
652
+ end
653
+ end
654
+ def default_sql_obfuscator(sql)
655
+ sql = sql.dup
656
+ # This is hardly readable. Use the unit tests.
657
+ # remove single quoted strings:
658
+ sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
659
+ # remove double quoted strings:
660
+ sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
661
+ # replace all number literals
662
+ sql.gsub!(/\d+/, "?")
663
+ sql
664
+ end
665
+ end
666
+
667
+ end
668
+ end