newrelic_rpm 2.12.3 → 2.13.0.beta3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of newrelic_rpm might be problematic. Click here for more details.

Files changed (118) hide show
  1. data/CHANGELOG +24 -2
  2. data/README.rdoc +172 -0
  3. data/bin/newrelic +13 -0
  4. data/bin/newrelic_cmd +2 -1
  5. data/install.rb +8 -45
  6. data/lib/new_relic/agent.rb +43 -30
  7. data/lib/new_relic/agent/agent.rb +699 -631
  8. data/lib/new_relic/agent/busy_calculator.rb +81 -81
  9. data/lib/new_relic/agent/error_collector.rb +9 -6
  10. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +2 -2
  11. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +10 -5
  12. data/lib/new_relic/agent/instrumentation/data_mapper.rb +13 -45
  13. data/lib/new_relic/agent/instrumentation/memcache.rb +15 -4
  14. data/lib/new_relic/agent/instrumentation/metric_frame.rb +37 -29
  15. data/lib/new_relic/agent/instrumentation/rack.rb +20 -34
  16. data/lib/new_relic/agent/method_tracer.rb +9 -9
  17. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +31 -31
  18. data/lib/new_relic/agent/stats_engine/metric_stats.rb +5 -5
  19. data/lib/new_relic/agent/stats_engine/samplers.rb +3 -0
  20. data/lib/new_relic/agent/transaction_sampler.rb +31 -15
  21. data/lib/new_relic/agent/worker_loop.rb +29 -28
  22. data/lib/new_relic/collection_helper.rb +4 -2
  23. data/lib/new_relic/command.rb +85 -0
  24. data/lib/new_relic/commands/deployments.rb +74 -114
  25. data/lib/new_relic/commands/install.rb +81 -0
  26. data/lib/new_relic/control.rb +54 -379
  27. data/lib/new_relic/control/configuration.rb +149 -0
  28. data/lib/new_relic/control/{external.rb → frameworks/external.rb} +2 -2
  29. data/lib/new_relic/control/{merb.rb → frameworks/merb.rb} +1 -1
  30. data/lib/new_relic/control/frameworks/rails.rb +126 -0
  31. data/lib/new_relic/control/{rails3.rb → frameworks/rails3.rb} +11 -10
  32. data/lib/new_relic/control/{ruby.rb → frameworks/ruby.rb} +1 -1
  33. data/lib/new_relic/control/{sinatra.rb → frameworks/sinatra.rb} +2 -2
  34. data/lib/new_relic/control/instrumentation.rb +84 -0
  35. data/lib/new_relic/control/logging_methods.rb +74 -0
  36. data/lib/new_relic/control/profiling.rb +24 -0
  37. data/lib/new_relic/control/server_methods.rb +88 -0
  38. data/lib/new_relic/local_environment.rb +3 -3
  39. data/lib/new_relic/metric_parser.rb +13 -2
  40. data/lib/new_relic/metric_parser/active_record.rb +4 -1
  41. data/lib/new_relic/metric_parser/apdex.rb +53 -0
  42. data/lib/new_relic/metric_parser/controller.rb +13 -5
  43. data/lib/new_relic/metric_parser/mem_cache.rb +1 -1
  44. data/lib/new_relic/metric_parser/other_transaction.rb +21 -0
  45. data/lib/new_relic/metric_spec.rb +3 -3
  46. data/lib/new_relic/noticed_error.rb +1 -1
  47. data/lib/new_relic/rack/developer_mode.rb +257 -0
  48. data/lib/new_relic/rack/metric_app.rb +56 -50
  49. data/lib/new_relic/rack/mongrel_rpm.ru +7 -6
  50. data/lib/new_relic/rack/newrelic.yml +4 -3
  51. data/lib/new_relic/rack_app.rb +2 -1
  52. data/lib/new_relic/recipes.rb +7 -7
  53. data/lib/new_relic/stats.rb +6 -14
  54. data/lib/new_relic/timer_lib.rb +27 -0
  55. data/lib/new_relic/transaction_analysis.rb +2 -7
  56. data/lib/new_relic/transaction_sample.rb +17 -85
  57. data/lib/new_relic/url_rule.rb +14 -0
  58. data/lib/new_relic/version.rb +3 -3
  59. data/lib/newrelic_rpm.rb +5 -9
  60. data/newrelic.yml +26 -9
  61. data/newrelic_rpm.gemspec +67 -32
  62. data/test/config/newrelic.yml +5 -0
  63. data/test/config/test_control.rb +6 -8
  64. data/test/new_relic/agent/active_record_instrumentation_test.rb +5 -6
  65. data/test/new_relic/agent/agent_controller_test.rb +18 -4
  66. data/test/new_relic/agent/agent_test_controller.rb +1 -6
  67. data/test/new_relic/agent/busy_calculator_test.rb +2 -0
  68. data/test/new_relic/agent/collection_helper_test.rb +6 -6
  69. data/test/new_relic/agent/error_collector_test.rb +13 -21
  70. data/test/new_relic/agent/metric_data_test.rb +3 -6
  71. data/test/new_relic/agent/rpm_agent_test.rb +121 -117
  72. data/test/new_relic/agent/task_instrumentation_test.rb +128 -133
  73. data/test/new_relic/agent/transaction_sample_test.rb +176 -170
  74. data/test/new_relic/agent/worker_loop_test.rb +24 -18
  75. data/test/new_relic/control_test.rb +13 -3
  76. data/test/new_relic/deployments_api_test.rb +7 -7
  77. data/test/new_relic/environment_test.rb +1 -1
  78. data/test/new_relic/metric_parser_test.rb +58 -4
  79. data/test/new_relic/rack/episodes_test.rb +317 -0
  80. data/test/new_relic/stats_test.rb +3 -2
  81. data/test/test_contexts.rb +28 -0
  82. data/test/test_helper.rb +24 -5
  83. data/ui/helpers/{newrelic_helper.rb → developer_mode_helper.rb} +63 -23
  84. data/ui/views/layouts/newrelic_default.rhtml +6 -6
  85. data/ui/views/newrelic/_explain_plans.rhtml +4 -4
  86. data/ui/views/newrelic/_sample.rhtml +18 -17
  87. data/ui/views/newrelic/_segment.rhtml +1 -0
  88. data/ui/views/newrelic/_segment_row.rhtml +8 -8
  89. data/ui/views/newrelic/_show_sample_detail.rhtml +1 -1
  90. data/ui/views/newrelic/_show_sample_sql.rhtml +2 -2
  91. data/ui/views/newrelic/_sql_row.rhtml +8 -3
  92. data/ui/views/newrelic/_stack_trace.rhtml +9 -24
  93. data/ui/views/newrelic/_table.rhtml +1 -1
  94. data/ui/views/newrelic/explain_sql.rhtml +3 -2
  95. data/ui/views/newrelic/{images → file/images}/arrow-close.png +0 -0
  96. data/ui/views/newrelic/{images → file/images}/arrow-open.png +0 -0
  97. data/ui/views/newrelic/{images → file/images}/blue_bar.gif +0 -0
  98. data/ui/views/newrelic/{images → file/images}/file_icon.png +0 -0
  99. data/ui/views/newrelic/{images → file/images}/gray_bar.gif +0 -0
  100. data/ui/views/newrelic/{images → file/images}/new-relic-rpm-desktop.gif +0 -0
  101. data/ui/views/newrelic/{images → file/images}/new_relic_rpm_desktop.gif +0 -0
  102. data/ui/views/newrelic/{images → file/images}/textmate.png +0 -0
  103. data/ui/views/newrelic/file/javascript/jquery-1.4.2.js +6240 -0
  104. data/ui/views/newrelic/file/javascript/transaction_sample.js +120 -0
  105. data/ui/views/newrelic/{stylesheets → file/stylesheets}/style.css +0 -0
  106. data/ui/views/newrelic/index.rhtml +7 -5
  107. data/ui/views/newrelic/sample_not_found.rhtml +1 -1
  108. data/ui/views/newrelic/show_sample.rhtml +5 -6
  109. metadata +92 -34
  110. data/README.md +0 -138
  111. data/lib/new_relic/commands/new_relic_commands.rb +0 -30
  112. data/lib/new_relic/control/rails.rb +0 -151
  113. data/test/new_relic/agent/mock_ar_connection.rb +0 -40
  114. data/test/ui/newrelic_controller_test.rb +0 -14
  115. data/test/ui/newrelic_helper_test.rb +0 -53
  116. data/ui/controllers/newrelic_controller.rb +0 -220
  117. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +0 -7288
  118. data/ui/views/newrelic/javascript/transaction_sample.js +0 -107
data/CHANGELOG CHANGED
@@ -1,3 +1,26 @@
1
+ v2.13.0
2
+ * developer mode is now a rack middleware and can be used on any framework;
3
+ it is no longer supported automatically on versions of Rails prior to 2.3;
4
+ see README for details
5
+ * memcache key recording for transaction traces
6
+ * use system_timer gem if available, fall back to timeout lib
7
+ * address instability issues in JRuby 1.2
8
+ * renamed executable 'newrelic_cmd' to 'newrelic'; old name still supported
9
+ for backward compatibility
10
+ * added 'newrelic install' command to install a newrelic.yml file in the
11
+ current directory
12
+ * optimization to execution time measurement
13
+ * optimization to startup sequence
14
+ * change startup sequence so that instrumentation is installed after all
15
+ other gems and plugins have loaded
16
+ * add option to override automatic flushing of data on exit--send_data_on_exit
17
+ defaults to 'true'
18
+ * ignored errors no longer affect apdex score
19
+ * added record_transaction method to the api to allow recording
20
+ details from web and background transactions occurring outside RPM
21
+ * fixed a bug related to enabling a gold trial / upgrade not sending
22
+ trasaction traces correctly
23
+
1
24
  v2.12.3
2
25
  * fix regression in startup sequence
3
26
 
@@ -22,7 +45,7 @@ v2.11.2
22
45
  * fix for unicorn not reporting when the proc line had 'master' in it
23
46
  * fix regression for passenger 2.0 and earlier
24
47
  * fix after_fork in the shim
25
-
48
+
26
49
  v2.11.1
27
50
  * republished gem without generated rdocs
28
51
 
@@ -40,7 +63,6 @@ v2.11.0
40
63
  * optimizations to background thread, controller instrumentation, memory
41
64
  usage
42
65
  * add logger method to public_api
43
- * support list notation for ignored exceptions in the newrelic.yml
44
66
 
45
67
  v2.10.8
46
68
  * fix bug in delayed_job instrumentation that caused the job queue sampler
data/README.rdoc ADDED
@@ -0,0 +1,172 @@
1
+ = New Relic RPM
2
+
3
+ New Relic RPM is a Ruby performance management system, developed by
4
+ New Relic, Inc (http://www.newrelic.com). RPM provides you with deep
5
+ information about the performance of your Ruby on Rails or Merb
6
+ application as it runs in production. The New Relic Agent is
7
+ dual-purposed as a either a Rails plugin or a Gem, hosted on
8
+ github[http://github.com/newrelic/rpm/tree/master] and Rubyforge.
9
+
10
+ The New Relic Agent runs in one of two modes:
11
+
12
+ ==== Production Mode
13
+
14
+ Low overhead instrumentation that captures detailed information on
15
+ your application running in production and transmits them to
16
+ rpm.newrelic.com where you can monitor them in real time.
17
+
18
+ ==== Developer Mode
19
+
20
+ A Rack middleware that maps /newrelic to an application for showing
21
+ detailed performance metrics on a page by page basis. Installed
22
+ automatically in Rails applications.
23
+
24
+ == Supported Environments
25
+
26
+ * Ruby 1.8.6, 1.8.7 or 1.9.1, including REE
27
+ * JRuby 1.3 minimum
28
+ * Rails 1.2.6 or later for Production Mode
29
+ * Rails 2.2 or later for Developer Mode
30
+ * Merb 1.0 or later
31
+ * Sinatra
32
+ * Rack
33
+
34
+ Any Rack based framework should work but may not be tested. Install
35
+ RPM as a gem and add the Developer Mode middleware if desired. Report
36
+ any problems to support@newrelic.com.
37
+
38
+ You can also monitor non-web applications. Refer to the "Other
39
+ Environments" section under "Getting Started".
40
+
41
+ = Getting Started
42
+
43
+ Install the RPM agent as a gem. If you are using Rails you can
44
+ install the gem as a plug-in--details below.
45
+
46
+ gem install newrelic_rpm
47
+
48
+ To monitor your applications in production, create an account at
49
+ http://newrelic.com/get-RPM.html. There you can
50
+ sign up for a free Lite account or one of our paid subscriptions.
51
+
52
+ Once you receive the welcome e-mail with a license key and
53
+ +newrelic.yml+ file, copy the +newrelic.yml+ file into your app config
54
+ directory.
55
+
56
+ All agent configuration is done in the +newrelic.yml+ file. This file
57
+ is by default read from the +config+ directory of the application root
58
+ and is subsequently searched for in the application root directory,
59
+ and then in a <tt>~/.newrelic</tt> directory
60
+
61
+ === Rails Installation
62
+
63
+ You can install the agent as a plug-in or a Gem. To install the agent
64
+ as a Rails plug-in, do the following:
65
+
66
+ script/plugin install http://newrelic.rubyforge.org/svn/newrelic_rpm
67
+
68
+ If using the Agent as a gem, edit +environment.rb+ and add to the initalizer block:
69
+
70
+ config.gem "newrelic_rpm"
71
+
72
+ === Merb Installation
73
+
74
+ To monitor a merb app install the newrelic_rpm gem and add
75
+
76
+ dependency 'newrelic_rpm'
77
+
78
+ to your init.rb file.
79
+
80
+ === Sinatra Installation
81
+
82
+ To use RPM with a Sinatra app, add
83
+ require 'newrelic_rpm'
84
+ in your Sinatra app, below the Sinatra require directive.
85
+
86
+ Then make sure you set RACK_ENV to the environment corresponding to the
87
+ configuration definitions in the newrelic.yml file; i.e., development,
88
+ staging, production, etc.
89
+
90
+ To use Developer Mode in Sinatra, add NewRelic::Rack::DeveloperMode to
91
+ the middleware stack. See the +config.ru+ sample below.
92
+
93
+ === Other Environments
94
+
95
+ You can use RPM to monitor any Ruby application. Add
96
+ require 'newrelic_rpm'
97
+ to your startup sequence and then manually start the agent using
98
+ NewRelic::Agent.manual_start
99
+
100
+ To instrument Rack based applications, refer to the docs in
101
+ NewRelic::Agent::Instrumentation::Rack.
102
+
103
+ Refer to the docs in NewRelic for details on how to monitor other web
104
+ frameworks, background jobs, and daemons.
105
+
106
+ Also, see if your environment is already supported by the
107
+ rpm_contrib[http://newrelic.github.com/rpm_contrib] gem.
108
+
109
+ == Developer Mode
110
+
111
+ When running the RPM Developer Mode, the RPM Agent will track the
112
+ performance of every HTTP request serviced by your application, and
113
+ store in memory this information for the last 100 HTTP transactions.
114
+
115
+ To view this performance information, including detailed SQL statement
116
+ analysis, open +/newrelic+ in your web application. For instance if
117
+ you are running mongrel or thin on port 3000, enter the following into
118
+ your browser:
119
+
120
+ http://localhost:3000/newrelic
121
+
122
+ Developer Mode is only initialized if the +developer_mode+ setting in
123
+ the newrelic.yml file is set to true. By default, it is turned off in
124
+ all environments but +development+.
125
+
126
+ ==== Developer Mode in Rails
127
+
128
+ Developer Mode is available automatically in Rails Applications
129
+ based on Rails 2.3 and later. No additional configuration is
130
+ required.
131
+
132
+ For earlier versions of Rails that support Rack, you can use
133
+ a +config.ru+ as below.
134
+
135
+ ==== Developer Mode in Rack Applications
136
+
137
+ Developer Mode is available for any Rack based application such as
138
+ Sinatra by installing the NewRelic::Rack::DeveloperMode
139
+ middleware. This middleware passes all requests that do not start with
140
+ /newrelic.
141
+
142
+ Here's an example entry for Developer Mode in a +config.ru+ file:
143
+
144
+ require 'new_relic/rack_app'
145
+ use NewRelic::Rack::DeveloperMode
146
+
147
+
148
+ == Production Mode
149
+
150
+ When your application runs in the production environment, the New
151
+ Relic agent runs in production mode. It connects to the New Relic RPM
152
+ service and sends deep performance data to the RPM service for your
153
+ analysis. To view this data, log in to http://rpm.newrelic.com.
154
+
155
+ NOTE: You must have a valid account and license key to view this data
156
+ online. Refer to instructions in *Getting Started*, below.
157
+
158
+
159
+ = Support
160
+
161
+ Reach out to us--and to fellow RPM users--at
162
+ support.newrelic.com[http://support.newrelic.com/discussions/support].
163
+ There you'll find documentation, FAQs, and forums where you can submit
164
+ suggestions and discuss RPM with New Relic staff and other users.
165
+
166
+ Find a bug? E-mail support@newrelic.com, or post it to
167
+ support.newrelic.com[http://support.newrelic.com/discussions/support].
168
+
169
+ Thank you, and may your application scale to infinity plus one.
170
+
171
+ Lew Cirne, Founder and CEO<br/>
172
+ New Relic, Inc.
data/bin/newrelic ADDED
@@ -0,0 +1,13 @@
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
+
5
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..','lib'))
6
+ require 'new_relic/command'
7
+ begin
8
+ NewRelic::Command.run
9
+ rescue NewRelic::Command::CommandFailure => failure
10
+ STDERR.puts failure.message
11
+ STDERR.puts failure.options if failure.options
12
+ exit 1
13
+ end
data/bin/newrelic_cmd CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # This command has been renamed "newrelic"
2
3
  # executes one of the commands in the new_relic/commands directory
3
4
  # pass the name of the command as an argument
4
- require File.dirname(__FILE__) + '/../lib/new_relic/commands/new_relic_commands'
5
+ load File.dirname(__FILE__) + '/newrelic'
data/install.rb CHANGED
@@ -1,46 +1,9 @@
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
1
  if __FILE__ == $0 || $0 =~ /script\/plugin/
45
- install_newrelic_config_file
46
- end
2
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
3
+ require 'new_relic/command'
4
+ begin
5
+ NewRelic::Command::Install.new(:quiet => true, :app_name => 'My Application').run
6
+ rescue NewRelic::Command::CommandFailure => e
7
+ $stderr.puts e.message
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
- # = New Relic Agent
1
+ # = New Relic RPM Agent
2
2
  #
3
3
  # New Relic RPM is a performance monitoring application for Ruby
4
4
  # applications running in production. For more information on RPM
@@ -10,43 +10,24 @@
10
10
  # or for monitoring and analysis at http://rpm.newrelic.com with just
11
11
  # about any Ruby application.
12
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.
35
- # This 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
13
+ # == Getting Started
14
+ # For instructions on installation and setup, see
15
+ # the README[link:./files/README_rdoc.html] file.
38
16
  #
39
17
  # == Using with Rack/Metal
40
18
  #
41
- # To instrument middlewares, refer to the docs in
19
+ # To instrument Rack middlwares or Metal apps, refer to the docs in
42
20
  # NewRelic::Agent::Instrumentation::Rack.
43
21
  #
44
22
  # == Agent API
45
23
  #
46
24
  # For details on the Agent API, refer to NewRelic::Agent.
25
+ #
26
+ # == Customizing RPM
47
27
  #
28
+ # For detailed information on customizing the RPM Agent
29
+ # please visit our {support and documentation site}[http://support.newrelic.com].
48
30
  #
49
- # :main: lib/new_relic/agent.rb
50
31
  module NewRelic
51
32
  # == Agent APIs
52
33
  # This module contains the public API methods for the Agent.
@@ -86,8 +67,10 @@ module NewRelic
86
67
  require 'new_relic/collection_helper'
87
68
  require 'new_relic/transaction_analysis'
88
69
  require 'new_relic/transaction_sample'
70
+ require 'new_relic/url_rule'
89
71
  require 'new_relic/noticed_error'
90
72
  require 'new_relic/histogram'
73
+ require 'new_relic/timer_lib'
91
74
 
92
75
  require 'new_relic/agent/chained_call'
93
76
  require 'new_relic/agent/agent'
@@ -109,7 +92,6 @@ module NewRelic
109
92
  require 'set'
110
93
  require 'thread'
111
94
  require 'resolv'
112
- require 'timeout'
113
95
 
114
96
  # An exception that is thrown by the server if the agent license is invalid.
115
97
  class LicenseException < StandardError; end
@@ -340,7 +322,8 @@ module NewRelic
340
322
  NewRelic::Agent::Instrumentation::MetricFrame.notice_error(exception, options)
341
323
  end
342
324
 
343
- # Add parameters to the current transaction trace on the call stack.
325
+ # Add parameters to the current transaction trace (and traced error if any)
326
+ # on the call stack.
344
327
  #
345
328
  def add_custom_parameters(params)
346
329
  NewRelic::Agent::Instrumentation::MetricFrame.add_custom_parameters(params)
@@ -365,5 +348,35 @@ module NewRelic
365
348
  yield
366
349
  end
367
350
  end
351
+
352
+ # Record a web transaction from an external source. This will
353
+ # process the response time, error, and score an apdex value.
354
+ #
355
+ # First argument is a float value, time in seconds. Option
356
+ # keys are strings.
357
+ #
358
+ # == Identifying the transaction
359
+ # * <tt>'uri' => uri</tt> to record the value for a given web request.
360
+ # If not provided, just record the aggregate dispatcher and apdex scores.
361
+ # * <tt>'metric' => metric_name</tt> to record with a general metric name
362
+ # like +OtherTransaction/Background/Class/method+. Ignored if +uri+ is
363
+ # provided.
364
+ #
365
+ # == Error options
366
+ # Provide one of the following:
367
+ # * <tt>'is_error' => true</tt> if an unknown error occurred
368
+ # * <tt>'error_message' => msg</tt> if an error message is available
369
+ # * <tt>'exception' => exception</tt> if a ruby exception is recorded
370
+ #
371
+ # == Misc options
372
+ # Additional information captured in errors
373
+ # * <tt>'referer' => referer_url</tt>
374
+ # * <tt>'request_params' => hash</tt> to record a set of name/value pairs as the
375
+ # request parameters.
376
+ # * <tt>'custom_params' => hash</tt> to record extra information in traced errors
377
+ #
378
+ def record_transaction(response_sec, options = {})
379
+ agent.record_transaction(response_sec, options)
380
+ end
368
381
  end
369
382
  end
@@ -1,668 +1,736 @@
1
1
  require 'socket'
2
- require 'net/https'
2
+ require 'net/https'
3
3
  require 'net/http'
4
4
  require 'logger'
5
5
  require 'zlib'
6
6
  require 'stringio'
7
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
8
  module NewRelic
12
9
  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
10
+
11
+ # The Agent is a singleton that is instantiated when the plugin is
12
+ # activated. It collects performance data from ruby applications
13
+ # in realtime as the application runs, and periodically sends that
14
+ # data to the NewRelic server.
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
+ # 14105: v8 (tag 2.10.3)
22
+ # (no v7)
23
+ # 10379: v6 (not tagged)
24
+ # 4078: v5 (tag 2.5.4)
25
+ # 2292: v4 (tag 2.3.6)
26
+ # 1754: v3 (tag 2.3.0)
27
+ # 534: v2 (shows up in 2.1.0, our first tag)
28
+
29
+
30
+ def initialize
106
31
 
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
32
+ @launch_time = Time.now
141
33
 
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
34
+ @metric_ids = {}
35
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
36
+ @stats_engine = NewRelic::Agent::StatsEngine.new
37
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new
38
+ @stats_engine.transaction_sampler = @transaction_sampler
39
+ @error_collector = NewRelic::Agent::ErrorCollector.new
159
40
 
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}"
41
+ @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
42
+
43
+ @last_harvest_time = Time.now
44
+ @obfuscator = method(:default_sql_obfuscator)
181
45
  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
46
+
47
+ module ClassMethods
48
+ # Should only be called by NewRelic::Control
49
+ def instance
50
+ @instance ||= self.new
51
+ end
199
52
  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
53
+
54
+ module InstanceMethods
55
+
56
+ attr_reader :obfuscator
57
+ attr_reader :stats_engine
58
+ attr_reader :transaction_sampler
59
+ attr_reader :error_collector
60
+ attr_reader :record_sql
61
+ attr_reader :histogram
62
+ attr_reader :metric_ids
63
+ attr_reader :url_rules
64
+
65
+ def record_transaction(duration_seconds, options={})
66
+ is_error = options['is_error'] || options['error_message'] || options['exception']
67
+ metric = options['metric']
68
+ metric ||= options['uri'] # normalize this with url rules
69
+ raise "metric or uri arguments required" unless metric
70
+ metric_info = NewRelic::MetricParser.for_metric_named(metric)
71
+
72
+ if metric_info.is_web_transaction?
73
+ NewRelic::Agent::Instrumentation::MetricFrame.record_apdex(metric_info, duration_seconds, duration_seconds, is_error)
74
+ histogram.process(duration_seconds)
75
+ end
76
+ metrics = metric_info.summary_metrics
77
+
78
+ metrics << metric
79
+ metrics.each do |name|
80
+ stats = stats_engine.get_stats_no_scope(name)
81
+ stats.record_data_point(duration_seconds)
82
+ end
83
+
84
+ if is_error
85
+ if error_message
86
+ e = Exception.new error_message if error_message
87
+ error_collector.notice_error e, :uri => uri, :metric => uri
88
+ end
89
+ end
90
+ # busy time ?
91
+ end
92
+
93
+ # This method is deprecated. Use NewRelic::Agent.manual_start
94
+ def manual_start(ignored=nil, also_ignored=nil)
95
+ raise "This method no longer supported. Instead use the class method NewRelic::Agent.manual_start"
96
+ end
97
+
98
+ # This method should be called in a forked process after a fork.
99
+ # It assumes the parent process initialized the agent, but does
100
+ # not assume the agent started.
101
+ #
102
+ # The call is idempotent, but not re-entrant.
103
+ #
104
+ # * It clears any metrics carried over from the parent process
105
+ # * Restarts the sampler thread if necessary
106
+ # * Initiates a new agent run and worker loop unless that was done
107
+ # in the parent process and +:force_reconnect+ is not true
108
+ #
109
+ # Options:
110
+ # * <tt>:force_reconnect => true</tt> to force the spawned process to
111
+ # establish a new connection, such as when forking a long running process.
112
+ # The default is false--it will only connect to the server if the parent
113
+ # had not connected.
114
+ # * <tt>:keep_retrying => false</tt> if we try to initiate a new
115
+ # connection, this tells me to only try it once so this method returns
116
+ # quickly if there is some kind of latency with the server.
117
+ def after_fork(options={})
118
+
119
+ # @connected gets false after we fail to connect or have an error
120
+ # connecting. @connected has nil if we haven't finished trying to connect.
121
+ # or we didn't attempt a connection because this is the master process
122
+
123
+ # log.debug "Agent received after_fork notice in #$$: [#{control.agent_enabled?}; monitor=#{control.monitor_mode?}; connected: #{@connected.inspect}; thread=#{@worker_thread.inspect}]"
124
+ return if !control.agent_enabled? or
125
+ !control.monitor_mode? or
126
+ @connected == false or
127
+ @worker_thread && @worker_thread.alive?
128
+
129
+ log.info "Starting the worker thread in #$$ after forking."
130
+
131
+ # Clear out stats that are left over from parent process
132
+ reset_stats
133
+
134
+ # Don't ever check to see if this is a spawner. If we're in a forked process
135
+ # I'm pretty sure we're not also forking new instances.
136
+ start_worker_thread(options)
137
+ @stats_engine.start_sampler_thread
138
+ end
139
+
140
+ # True if we have initialized and completed 'start'
141
+ def started?
142
+ @started
143
+ end
224
144
 
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 } }
145
+ # Return nil if not yet connected, true if successfully started
146
+ # and false if we failed to start.
147
+ def connected?
148
+ @connected
234
149
  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
150
 
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
151
+ # Attempt a graceful shutdown of the agent.
152
+ def shutdown
153
+ return if not started?
154
+ if @worker_loop
155
+ @worker_loop.stop
156
+
157
+ log.debug "Starting Agent shutdown"
158
+
159
+ # if litespeed, then ignore all future SIGUSR1 - it's
160
+ # litespeed trying to shut us down
161
+
162
+ if control.dispatcher == :litespeed
163
+ Signal.trap("SIGUSR1", "IGNORE")
164
+ Signal.trap("SIGTERM", "IGNORE")
165
+ end
166
+
167
+ begin
168
+ NewRelic::Agent.disable_all_tracing do
169
+ graceful_disconnect
170
+ end
171
+ rescue => e
172
+ log.error e
173
+ log.error e.backtrace.join("\n")
174
+ end
175
+ end
176
+ @started = nil
177
+ end
178
+
179
+ def start_transaction
180
+ @stats_engine.start_transaction
181
+ end
182
+
183
+ def end_transaction
184
+ @stats_engine.end_transaction
185
+ end
186
+
187
+ def set_record_sql(should_record)
188
+ prev = Thread::current[:record_sql]
189
+ Thread::current[:record_sql] = should_record
190
+ prev.nil? || prev
191
+ end
192
+
193
+ def set_record_tt(should_record)
194
+ prev = Thread::current[:record_tt]
195
+ Thread::current[:record_tt] = should_record
196
+ prev.nil? || prev
197
+ end
198
+ # Push flag indicating whether we should be tracing in this
199
+ # thread.
200
+ def push_trace_execution_flag(should_trace=false)
201
+ (Thread.current[:newrelic_untraced] ||= []) << should_trace
202
+ end
203
+
204
+ # Pop the current trace execution status. Restore trace execution status
205
+ # to what it was before we pushed the current flag.
206
+ def pop_trace_execution_flag
207
+ Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
208
+ end
209
+
210
+ def set_sql_obfuscator(type, &block)
211
+ if type == :before
212
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
213
+ elsif type == :after
214
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
215
+ elsif type == :replace
216
+ @obfuscator = block
217
+ else
218
+ fail "unknown sql_obfuscator type #{type}"
219
+ end
220
+ end
221
+
222
+ def log
223
+ NewRelic::Agent.logger
224
+ end
225
+
226
+ # Start up the agent. This verifies that the agent_enabled? is
227
+ # true and initializes the sampler based on the current
228
+ # configuration settings. Then it will fire up the background
229
+ # thread for sending data to the server if applicable.
230
+ def start
231
+ if started?
232
+ control.log! "Agent Started Already!", :error
233
+ return
234
+ end
235
+ return if !control.agent_enabled?
236
+ @started = true
237
+ @local_host = determine_host
238
+
239
+ if control.dispatcher.nil? || control.dispatcher.to_s.empty?
240
+ log.info "No dispatcher detected."
241
+ else
242
+ log.info "Dispatcher: #{control.dispatcher.to_s}"
243
+ end
244
+ log.info "Application: #{control.app_names.join(", ")}" unless control.app_names.empty?
245
+
246
+ sampler_config = control.fetch('transaction_tracer', {})
247
+ # TODO: Should move this state into the transaction sampler instance
248
+ @should_send_samples = @config_should_send_samples = sampler_config.fetch('enabled', true)
249
+ @should_send_random_samples = sampler_config.fetch('random_sample', false)
250
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
251
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
252
+ @record_sql = sampler_config.fetch('record_sql', :obfuscated).to_sym
253
+
254
+ # use transaction_threshold: 4.0 to force the TT collection
255
+ # threshold to 4 seconds
256
+ # use transaction_threshold: apdex_f to use your apdex t value
257
+ # multiplied by 4
258
+ # undefined transaction_threshold defaults to 2.0
259
+ apdex_f = 4 * NewRelic::Control.instance.apdex_t
260
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0)
261
+ if @slowest_transaction_threshold =~ /apdex_f/i
262
+ @slowest_transaction_threshold = apdex_f
263
+ end
264
+ @slowest_transaction_threshold = @slowest_transaction_threshold.to_f
265
+
266
+ log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
267
+
268
+ case
269
+ when !control.monitor_mode?
270
+ log.warn "Agent configured not to send data in this environment - edit newrelic.yml to change this"
271
+ when !control.license_key
272
+ log.error "No license key found. Please edit your newrelic.yml file and insert your license key."
273
+ when control.license_key.length != 40
274
+ log.error "Invalid license key: #{control.license_key}"
275
+ when [:passenger, :unicorn].include?(control.dispatcher)
276
+ log.info "Connecting workers after forking."
277
+ else
278
+ # Do the connect in the foreground if we are in sync mode
279
+ NewRelic::Agent.disable_all_tracing { connect(:keep_retrying => false) } if control.sync_startup
280
+
281
+ # Start the event loop and initiate connection if necessary
282
+ start_worker_thread
283
+
284
+ # Our shutdown handler needs to run after other shutdown handlers
285
+ # that may be doing things like running the app (hello sinatra).
286
+ if control.send_data_on_exit
287
+ if RUBY_VERSION =~ /rubinius/i
288
+ list = at_exit { shutdown }
289
+ # move the shutdown handler to the front of the list, to
290
+ # execute last:
291
+ list.unshift(list.pop)
292
+ elsif !defined?(JRuby) or !defined?(Sinatra::Application)
293
+ at_exit { at_exit { shutdown } }
294
+ end
295
+ end
296
+ end
297
+ log.info "New Relic RPM Agent #{NewRelic::VERSION::STRING} Initialized: pid = #$$"
298
+ log.info "Agent Log found in #{NewRelic::Control.instance.log_file}" if NewRelic::Control.instance.log_file
299
+ end
300
+
301
+ # Clear out the metric data, errors, and transaction traces. Reset the histogram data.
302
+ def reset_stats
303
+ @stats_engine.reset_stats
304
+ @unsent_errors = []
305
+ @traces = nil
306
+ @unsent_timeslice_data = {}
307
+ @last_harvest_time = Time.now
308
+ @launch_time = Time.now
309
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
310
+ end
311
+
312
+ private
313
+ def collector
314
+ @collector ||= control.server
315
+ end
316
+
317
+ # Try to launch the worker thread and connect to the server.
318
+ #
319
+ # See #connect for a description of connection_options.
320
+ def start_worker_thread(connection_options = {})
321
+ log.debug "Creating RPM worker thread."
322
+ @worker_thread = Thread.new do
323
+ begin
324
+ NewRelic::Agent.disable_all_tracing do
325
+ # We try to connect. If this returns false that means
326
+ # the server rejected us for a licensing reason and we should
327
+ # just exit the thread. If it returns nil
328
+ # that means it didn't try to connect because we're in the master.
329
+ connect(connection_options)
330
+ if @connected
331
+ # disable transaction sampling if disabled by the server and we're not in dev mode
332
+ if !control.developer_mode? && !@should_send_samples
333
+ @transaction_sampler.disable
334
+ else
335
+ @transaction_sampler.enable # otherwise ensure TT's are enabled
336
+ end
337
+ log.info "Reporting performance data every #{@report_period} seconds."
338
+ log.debug "Running worker loop"
339
+ # Note if the agent attempts to report more frequently than allowed by the server
340
+ # the server will start dropping data.
341
+ @worker_loop = WorkerLoop.new
342
+ @worker_loop.run(@report_period) do
343
+ harvest_and_send_timeslice_data
344
+ harvest_and_send_slowest_sample if @should_send_samples
345
+ harvest_and_send_errors if error_collector.enabled
346
+ end
347
+ else
348
+ log.debug "No connection. Worker thread finished."
349
+ end
350
+ end
351
+ rescue NewRelic::Agent::ForceRestartException => e
352
+ log.info e.message
353
+ # disconnect and start over.
354
+ # clear the stats engine
355
+ reset_stats
356
+ @metric_ids = {}
357
+ @connected = nil
358
+ # Wait a short time before trying to reconnect
359
+ sleep 30
360
+ retry
361
+ rescue NewRelic::Agent::ForceDisconnectException => e
362
+ # when a disconnect is requested, stop the current thread, which
363
+ # is the worker thread that gathers data and talks to the
364
+ # server.
365
+ log.error "RPM forced this agent to disconnect (#{e.message})"
366
+ @connected = false
367
+ rescue NewRelic::Agent::ServerConnectionException => e
368
+ log.error "Unable to establish connection with the server. Run with log level set to debug for more information."
369
+ log.debug("#{e.class.name}: #{e.message}\n#{e.backtrace.first}")
370
+ @connected = false
371
+ rescue Exception => e
372
+ log.error "Terminating worker loop: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
373
+ @connected = false
374
+ end # begin
375
+ end # thread new
376
+ @worker_thread['newrelic_label'] = 'Worker Loop'
377
+ end
378
+
379
+ def control
380
+ NewRelic::Control.instance
381
+ end
382
+
383
+ # Connect to the server and validate the license. If successful,
384
+ # @connected has true when finished. If not successful, you can
385
+ # keep calling this. Return false if we could not establish a
386
+ # connection with the server and we should not retry, such as if
387
+ # there's a bad license key.
388
+ #
389
+ # Set keep_retrying=false to disable retrying and return asap, such as when
390
+ # invoked in the foreground. Otherwise this runs until a successful
391
+ # connection is made, or the server rejects us.
392
+ #
393
+ # * <tt>:keep_retrying => false</tt> to only try to connect once, and
394
+ # return with the connection set to nil. This ensures we may try again
395
+ # later (default true).
396
+ # * <tt>force_reconnect => true</tt> if you want to establish a new connection
397
+ # to the server before running the worker loop. This means you get a separate
398
+ # agent run and RPM sees it as a separate instance (default is false).
399
+ def connect(options)
400
+ # Don't proceed if we already connected (@connected=true) or if we tried
401
+ # to connect and were rejected with prejudice because of a license issue
402
+ # (@connected=false).
403
+ return if !@connected.nil? && !options[:force_reconnect]
404
+ keep_retrying = options[:keep_retrying].nil? || options[:keep_retrying]
405
+
406
+ # wait a few seconds for the web server to boot, necessary in development
407
+ connect_retry_period = keep_retrying ? 10 : 0
408
+ connect_attempts = 0
409
+ @agent_id = nil
410
+ begin
411
+ sleep connect_retry_period.to_i
412
+ log.debug "Connecting Process to RPM: #$0"
413
+ host = invoke_remote(:get_redirect_host)
414
+ @collector = control.server_from_host(host) if host
415
+ environment = control['send_environment_info'] != false ? control.local_env.snapshot : []
416
+ log.debug "Connecting with validation seed/token: #{control.validate_seed}/#{control.validate_token}" if control.validate_seed
417
+ connect_data = invoke_remote :connect,
418
+ :pid => $$,
419
+ :host => @local_host,
420
+ :app_name => control.app_names,
421
+ :language => 'ruby',
422
+ :agent_version => NewRelic::VERSION::STRING,
423
+ :environment => environment,
424
+ :settings => control.settings,
425
+ :validate => {:seed => control.validate_seed,
426
+ :token => control.validate_token }
427
+
428
+ @agent_id = connect_data['agent_run_id']
429
+ @report_period = connect_data['data_report_period']
430
+ @url_rules = connect_data['url_rules']
431
+
432
+ control.log! "Connected to NewRelic Service at #{@collector}"
433
+ log.debug "Agent Run = #{@agent_id}."
434
+ log.debug "Connection data = #{connect_data.inspect}"
435
+
436
+ # Ask the server for permission to send transaction samples.
437
+ # determined by subscription license.
438
+ @should_send_samples = @config_should_send_samples && connect_data['collect_traces']
439
+
440
+ if @should_send_samples
441
+ if @should_send_random_samples
442
+ @transaction_sampler.random_sampling = true
443
+ @transaction_sampler.sampling_rate = connect_data['sampling_rate']
444
+ log.info "Transaction sampling enabled, rate = #{@transaction_sampler.sampling_rate}"
273
445
  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
446
+ log.debug "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
447
+ else
448
+ log.debug "Transaction traces will not be sent to the RPM service."
449
+ end
450
+
451
+ # Ask for permission to collect error data
452
+ error_collector.enabled = error_collector.config_enabled && connect_data['collect_errors']
453
+
454
+ log.debug "Errors will be sent to the RPM service." if error_collector.enabled
455
+
456
+ @connected_pid = $$
457
+ @connected = true
458
+
459
+ rescue NewRelic::Agent::LicenseException => e
460
+ log.error e.message
461
+ log.info "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
462
+ @connected = false
463
+
464
+ rescue Timeout::Error, StandardError => e
465
+ if e.instance_of? NewRelic::Agent::ServerConnectionException
466
+ log.info "Unable to establish connection with New Relic RPM Service at #{control.server}: #{e.message}"
467
+ log.debug e.backtrace.join("\n")
468
+ else
469
+ log.error "Error establishing connection with New Relic RPM Service at #{control.server}: #{e.message}"
470
+ log.debug e.backtrace.join("\n")
471
+ end
472
+ # retry logic
473
+ if keep_retrying
474
+ connect_attempts += 1
475
+ case connect_attempts
476
+ when 1..2
477
+ connect_retry_period, period_msg = 60, "1 minute"
478
+ when 3..5
479
+ connect_retry_period, period_msg = 60 * 2, "2 minutes"
480
+ else
481
+ connect_retry_period, period_msg = 5 * 60, "5 minutes"
283
482
  end
483
+ log.info "Will re-attempt in #{period_msg}"
484
+ retry
284
485
  else
285
- log.debug "No connection. Worker thread finished."
486
+ @connected = nil
286
487
  end
287
488
  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."
489
+ end
490
+
491
+ def determine_host
492
+ Socket.gethostname
493
+ end
494
+
495
+ def determine_home_directory
496
+ control.root
395
497
  end
396
498
 
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"
499
+ def is_application_spawner?
500
+ $0 =~ /ApplicationSpawner|^unicorn\S* master/
501
+ end
502
+
503
+ def harvest_and_send_timeslice_data
504
+
505
+ NewRelic::Agent::BusyCalculator.harvest_busy
506
+
507
+ now = Time.now
508
+
509
+ @unsent_timeslice_data ||= {}
510
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
511
+
512
+ begin
513
+ # In this version of the protocol, we get back an assoc array of spec to id.
514
+ metric_ids = invoke_remote(:metric_data, @agent_id,
515
+ @last_harvest_time.to_f,
516
+ now.to_f,
517
+ @unsent_timeslice_data.values)
518
+
519
+ rescue Timeout::Error
520
+ # assume that the data was received. chances are that it was
521
+ metric_ids = nil
427
522
  end
428
- log.info "Will re-attempt in #{period_msg}"
429
- retry
430
- else
431
- @connected = nil
523
+
524
+ metric_ids.each do | spec, id |
525
+ @metric_ids[spec] = id
526
+ end if metric_ids
527
+
528
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
529
+
530
+ # if we successfully invoked this web service, then clear the unsent message cache.
531
+ @unsent_timeslice_data = {}
532
+ @last_harvest_time = now
533
+
534
+ # handle_messages
535
+
536
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
537
+ # then the metric data is downsampled for another timeslices
432
538
  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
539
+
540
+ def harvest_and_send_slowest_sample
541
+ @traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
542
+
543
+ unless @traces.empty?
544
+ now = Time.now
545
+ log.debug "Sending (#{@traces.length}) transaction traces"
546
+ begin
547
+ # take the traces and prepare them for sending across the
548
+ # wire. This includes gathering SQL explanations, stripping
549
+ # out stack traces, and normalizing SQL. note that we
550
+ # explain only the sql statements whose segments' execution
551
+ # times exceed our threshold (to avoid unnecessary overhead
552
+ # of running explains on fast queries.)
553
+ options = { :keep_backtraces => true }
554
+ options[:record_sql] = @record_sql unless @record_sql == :off
555
+ options[:explain_sql] = @explain_threshold if @explain_enabled
556
+ traces = @traces.collect {|trace| trace.prepare_to_send(options)}
557
+ invoke_remote :transaction_sample_data, @agent_id, traces
558
+ rescue PostTooBigException
559
+ # we tried to send too much data, drop the first trace and
560
+ # try again
561
+ retry if @traces.shift
562
+ end
563
+
564
+ log.debug "Sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
565
+ end
566
+
567
+ # if we succeed sending this sample, then we don't need to keep
568
+ # the slowest sample around - it has been sent already and we
569
+ # can collect the next one
570
+ @traces = nil
571
+
572
+ # note - exceptions are logged in invoke_remote. If an
573
+ # exception is encountered here, then the slowest sample of is
574
+ # determined of the entire period since the last reported
575
+ # sample.
500
576
  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
577
 
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
578
+ def harvest_and_send_errors
579
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
580
+ if @unsent_errors && @unsent_errors.length > 0
581
+ log.debug "Sending #{@unsent_errors.length} errors"
582
+ begin
583
+ invoke_remote :error_data, @agent_id, @unsent_errors
584
+ rescue PostTooBigException
585
+ @unsent_errors.shift
586
+ retry
587
+ end
588
+ # if the remote invocation fails, then we never clear
589
+ # @unsent_errors, and therefore we can re-attempt to send on
590
+ # the next heartbeat. Note the error collector maxes out at
591
+ # 20 instances to prevent leakage
592
+ @unsent_errors = []
593
+ end
594
+ end
564
595
 
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
596
+ def compress_data(object)
597
+ dump = Marshal.dump(object)
592
598
 
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
599
+ # this checks to make sure mongrel won't choke on big uploads
600
+ check_post_size(dump)
602
601
 
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
602
+ # we currently optimize for CPU here since we get roughly a 10x
603
+ # reduction in message size with this, and CPU overhead is at a
604
+ # premium. For extra-large posts, we use the higher compression
605
+ # since otherwise it actually errors out.
606
+
607
+ dump_size = dump.size
608
+
609
+ # Compress if content is smaller than 64kb. There are problems
610
+ # with bugs in Ruby in some versions that expose us to a risk of
611
+ # segfaults if we compress aggressively.
612
+ return [dump, 'identity'] if dump_size < (64*1024)
613
+
614
+ # medium payloads get fast compression, to save CPU
615
+ # big payloads get all the compression possible, to stay under
616
+ # the 2,000,000 byte post threshold
617
+ compression = dump_size < 2000000 ? Zlib::BEST_SPEED : Zlib::BEST_COMPRESSION
618
+
619
+ [Zlib::Deflate.deflate(dump, compression), 'deflate']
620
+ end
621
+
622
+ def check_post_size(post_string)
623
+ # TODO: define this as a config option on the server side
624
+ return if post_string.size < control.post_size_limit
625
+ log.warn "Tried to send too much data: #{post_string.size} bytes"
626
+ raise PostTooBigException
627
+ end
628
+
629
+ def send_request(opts)
630
+ request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'HOST' => opts[:collector].name)
631
+ request.content_type = "application/octet-stream"
632
+ request.body = opts[:data]
633
+
634
+ log.debug "Connect to #{opts[:collector]}#{opts[:uri]}"
635
+
636
+ response = nil
637
+ http = control.http_connection(collector)
638
+ http.read_timeout = nil
639
+ begin
640
+ NewRelic::TimerLib.timeout(@request_timeout) do
641
+ response = http.request(request)
642
+ end
643
+ rescue Timeout::Error
644
+ log.warn "Timed out trying to post data to RPM (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
645
+ raise
646
+ end
647
+ if response.is_a? Net::HTTPServiceUnavailable
648
+ raise NewRelic::Agent::ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}"
649
+ elsif response.is_a? Net::HTTPGatewayTimeOut
650
+ log.debug("Timed out getting response: #{response.message}")
651
+ raise Timeout::Error, response.message
652
+ elsif response.is_a? Net::HTTPRequestEntityTooLarge
653
+ raise PostTooBigException
654
+ elsif !(response.is_a? Net::HTTPSuccess)
655
+ raise NewRelic::Agent::ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}"
656
+ end
657
+ response
658
+ end
659
+
660
+ def decompress_response(response)
661
+ if response['content-encoding'] != 'gzip'
662
+ log.debug "Uncompressed content returned"
663
+ return response.body
664
+ end
665
+ log.debug "Decompressing return value"
666
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
667
+ i.read
668
+ end
669
+
670
+ def check_for_exception(response)
671
+ dump = decompress_response(response)
672
+ value = Marshal.load(dump)
673
+ raise value if value.is_a? Exception
674
+ value
675
+ end
676
+
677
+ def remote_method_uri(method)
678
+ uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
679
+ uri << "?run_id=#{@agent_id}" if @agent_id
680
+ uri
681
+ end
682
+
683
+ # send a message via post
684
+ def invoke_remote(method, *args)
685
+ #determines whether to zip the data or send plain
686
+ post_data, encoding = compress_data(args)
687
+
688
+ response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
689
+
690
+ # raises the right exception if the remote server tells it to die
691
+ return check_for_exception(response)
692
+ rescue NewRelic::Agent::ForceRestartException => e
693
+ log.info e.message
694
+ raise
695
+ rescue SystemCallError, SocketError => e
696
+ # These include Errno connection errors
697
+ raise NewRelic::Agent::ServerConnectionException, "Recoverable error connecting to the server: #{e}"
698
+ end
699
+
700
+ def graceful_disconnect
701
+ if @connected
702
+ begin
703
+ @request_timeout = 10
704
+ log.debug "Flushing unsent metric data to server"
705
+ @worker_loop.run_task
706
+ if @connected_pid == $$
707
+ log.debug "Sending RPM service agent run shutdown message"
708
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
709
+ else
710
+ log.debug "This agent connected from parent process #{@connected_pid}--not sending shutdown"
711
+ end
712
+ log.debug "Graceful disconnect complete"
713
+ rescue Timeout::Error, StandardError
714
+ end
644
715
  else
645
- log.debug "This agent connected from #{@connected_pid}--not sending shutdown"
716
+ log.debug "Bypassing graceful disconnect - agent not connected"
646
717
  end
647
- log.debug "Graceful disconnect complete"
648
- rescue Timeout::Error, StandardError
649
718
  end
650
- else
651
- log.debug "Bypassing graceful disconnect - agent not connected"
719
+ def default_sql_obfuscator(sql)
720
+ sql = sql.dup
721
+ # This is hardly readable. Use the unit tests.
722
+ # remove single quoted strings:
723
+ sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
724
+ # remove double quoted strings:
725
+ sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
726
+ # replace all number literals
727
+ sql.gsub!(/\d+/, "?")
728
+ sql
729
+ end
652
730
  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
731
+
732
+ extend ClassMethods
733
+ include InstanceMethods
664
734
  end
665
735
  end
666
-
667
- end
668
736
  end