newrelic_rpm 2.8.0
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.
- data/LICENSE +37 -0
- data/README +93 -0
- data/Rakefile +38 -0
- data/install.rb +37 -0
- data/lib/new_relic/agent.rb +26 -0
- data/lib/new_relic/agent/agent.rb +762 -0
- data/lib/new_relic/agent/chained_call.rb +13 -0
- data/lib/new_relic/agent/collection_helper.rb +81 -0
- data/lib/new_relic/agent/error_collector.rb +105 -0
- data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +95 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +151 -0
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
- data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +105 -0
- data/lib/new_relic/agent/instrumentation/memcache.rb +18 -0
- data/lib/new_relic/agent/instrumentation/merb/controller.rb +17 -0
- data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +15 -0
- data/lib/new_relic/agent/instrumentation/merb/errors.rb +6 -0
- data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +35 -0
- data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
- data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +30 -0
- data/lib/new_relic/agent/instrumentation/rails/errors.rb +23 -0
- data/lib/new_relic/agent/instrumentation/rails/rails.rb +6 -0
- data/lib/new_relic/agent/method_tracer.rb +171 -0
- data/lib/new_relic/agent/patch_const_missing.rb +31 -0
- data/lib/new_relic/agent/samplers/cpu.rb +29 -0
- data/lib/new_relic/agent/samplers/memory.rb +55 -0
- data/lib/new_relic/agent/samplers/mongrel.rb +26 -0
- data/lib/new_relic/agent/stats_engine.rb +241 -0
- data/lib/new_relic/agent/synchronize.rb +40 -0
- data/lib/new_relic/agent/transaction_sampler.rb +281 -0
- data/lib/new_relic/agent/worker_loop.rb +128 -0
- data/lib/new_relic/api/deployments.rb +92 -0
- data/lib/new_relic/config.rb +194 -0
- data/lib/new_relic/config/merb.rb +35 -0
- data/lib/new_relic/config/rails.rb +113 -0
- data/lib/new_relic/config/ruby.rb +9 -0
- data/lib/new_relic/local_environment.rb +108 -0
- data/lib/new_relic/merbtasks.rb +6 -0
- data/lib/new_relic/metric_data.rb +26 -0
- data/lib/new_relic/metric_spec.rb +39 -0
- data/lib/new_relic/metrics.rb +7 -0
- data/lib/new_relic/noticed_error.rb +21 -0
- data/lib/new_relic/shim_agent.rb +95 -0
- data/lib/new_relic/stats.rb +359 -0
- data/lib/new_relic/transaction_analysis.rb +122 -0
- data/lib/new_relic/transaction_sample.rb +499 -0
- data/lib/new_relic/version.rb +111 -0
- data/lib/new_relic_api.rb +275 -0
- data/lib/newrelic_rpm.rb +27 -0
- data/lib/tasks/agent_tests.rake +14 -0
- data/lib/tasks/all.rb +4 -0
- data/lib/tasks/install.rake +7 -0
- data/newrelic.yml +137 -0
- data/recipes/newrelic.rb +46 -0
- data/test/config/newrelic.yml +26 -0
- data/test/config/test_config.rb +9 -0
- data/test/new_relic/agent/mock_ar_connection.rb +40 -0
- data/test/new_relic/agent/mock_scope_listener.rb +23 -0
- data/test/new_relic/agent/model_fixture.rb +17 -0
- data/test/new_relic/agent/tc_active_record.rb +91 -0
- data/test/new_relic/agent/tc_agent.rb +112 -0
- data/test/new_relic/agent/tc_collection_helper.rb +104 -0
- data/test/new_relic/agent/tc_controller.rb +98 -0
- data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +52 -0
- data/test/new_relic/agent/tc_error_collector.rb +127 -0
- data/test/new_relic/agent/tc_method_tracer.rb +306 -0
- data/test/new_relic/agent/tc_stats_engine.rb +218 -0
- data/test/new_relic/agent/tc_synchronize.rb +37 -0
- data/test/new_relic/agent/tc_transaction_sample.rb +175 -0
- data/test/new_relic/agent/tc_transaction_sample_builder.rb +200 -0
- data/test/new_relic/agent/tc_transaction_sampler.rb +305 -0
- data/test/new_relic/agent/tc_worker_loop.rb +101 -0
- data/test/new_relic/agent/testable_agent.rb +13 -0
- data/test/new_relic/tc_config.rb +36 -0
- data/test/new_relic/tc_deployments_api.rb +37 -0
- data/test/new_relic/tc_environment.rb +94 -0
- data/test/new_relic/tc_metric_spec.rb +150 -0
- data/test/new_relic/tc_shim_agent.rb +9 -0
- data/test/new_relic/tc_stats.rb +141 -0
- data/test/test_helper.rb +39 -0
- data/test/ui/tc_newrelic_helper.rb +44 -0
- data/ui/controllers/newrelic_controller.rb +200 -0
- data/ui/helpers/google_pie_chart.rb +55 -0
- data/ui/helpers/newrelic_helper.rb +286 -0
- data/ui/views/layouts/newrelic_default.rhtml +49 -0
- data/ui/views/newrelic/_explain_plans.rhtml +27 -0
- data/ui/views/newrelic/_sample.rhtml +12 -0
- data/ui/views/newrelic/_segment.rhtml +28 -0
- data/ui/views/newrelic/_segment_row.rhtml +14 -0
- data/ui/views/newrelic/_show_sample_detail.rhtml +22 -0
- data/ui/views/newrelic/_show_sample_sql.rhtml +19 -0
- data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
- data/ui/views/newrelic/_sql_row.rhtml +11 -0
- data/ui/views/newrelic/_stack_trace.rhtml +30 -0
- data/ui/views/newrelic/_table.rhtml +12 -0
- data/ui/views/newrelic/explain_sql.rhtml +45 -0
- data/ui/views/newrelic/images/arrow-close.png +0 -0
- data/ui/views/newrelic/images/arrow-open.png +0 -0
- data/ui/views/newrelic/images/blue_bar.gif +0 -0
- data/ui/views/newrelic/images/gray_bar.gif +0 -0
- data/ui/views/newrelic/index.rhtml +37 -0
- data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
- data/ui/views/newrelic/sample_not_found.rhtml +2 -0
- data/ui/views/newrelic/show_sample.rhtml +62 -0
- data/ui/views/newrelic/show_source.rhtml +3 -0
- data/ui/views/newrelic/stylesheets/style.css +394 -0
- metadata +180 -0
data/LICENSE
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Copyright (c) 2008 New Relic, Inc. All rights reserved.
|
2
|
+
|
3
|
+
Certain inventions disclosed in this file may be claimed within
|
4
|
+
patents owned or patent applications filed by New Relic, Inc. or third
|
5
|
+
parties.
|
6
|
+
|
7
|
+
Subject to the terms of this notice, New Relic grants you a
|
8
|
+
nonexclusive, nontransferable license, without the right to
|
9
|
+
sublicense, to (a) install and execute one copy of these files on any
|
10
|
+
number of workstations owned or controlled by you and (b) distribute
|
11
|
+
verbatim copies of these files to third parties. As a condition to the
|
12
|
+
foregoing grant, you must provide this notice along with each copy you
|
13
|
+
distribute and you must not remove, alter, or obscure this notice. All
|
14
|
+
other use, reproduction, modification, distribution, or other
|
15
|
+
exploitation of these files is strictly prohibited, except as may be set
|
16
|
+
forth in a separate written license agreement between you and New
|
17
|
+
Relic. The terms of any such license agreement will control over this
|
18
|
+
notice. The license stated above will be automatically terminated and
|
19
|
+
revoked if you exceed its scope or violate any of the terms of this
|
20
|
+
notice.
|
21
|
+
|
22
|
+
This License does not grant permission to use the trade names,
|
23
|
+
trademarks, service marks, or product names of New Relic, except as
|
24
|
+
required for reasonable and customary use in describing the origin of
|
25
|
+
this file and reproducing the content of this notice. You may not
|
26
|
+
mark or brand this file with any trade name, trademarks, service
|
27
|
+
marks, or product names other than the original brand (if any)
|
28
|
+
provided by New Relic.
|
29
|
+
|
30
|
+
Unless otherwise expressly agreed by New Relic in a separate written
|
31
|
+
license agreement, these files are provided AS IS, WITHOUT WARRANTY OF
|
32
|
+
ANY KIND, including without any implied warranties of MERCHANTABILITY,
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE, or NON-INFRINGEMENT. As a
|
34
|
+
condition to your use of these files, you are solely responsible for
|
35
|
+
such use. New Relic will have no liability to you for direct,
|
36
|
+
indirect, consequential, incidental, special, or punitive damages or
|
37
|
+
for lost profits or data.
|
data/README
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= New Relic RPM
|
2
|
+
|
3
|
+
* http://www.newrelic.com
|
4
|
+
|
5
|
+
New Relic RPM is a Ruby performance management system, developed by New Relic, Inc.
|
6
|
+
RPM provides you with deep information about the performance of your Ruby on Rails
|
7
|
+
or Merb application as it runs in production. The New Relic Agent is distributed as
|
8
|
+
a either a Rails plugin or a Gem, both hosted on RubyForge.
|
9
|
+
|
10
|
+
The New Relic Agent runs in one of two modes:
|
11
|
+
|
12
|
+
* Developer Mode : Adds a web interface mapped to /newrelic to your application for
|
13
|
+
showing detailed performance metrics on a page by page basis.
|
14
|
+
|
15
|
+
* Production Mode : Low overhead instrumentation that captures detailed information
|
16
|
+
on your application running in production and transmits them to rpm.newrelic.com
|
17
|
+
where you can monitor them in real time.
|
18
|
+
|
19
|
+
=== DEVELOPER MODE
|
20
|
+
|
21
|
+
Developer mode is on by default when you run your application in the development
|
22
|
+
environment (but not when it runs in other environments.) When running in
|
23
|
+
developer mode, RPM will track the performance of every http request serviced
|
24
|
+
by your application, and store in memory this information for the last 100 http
|
25
|
+
transactions.
|
26
|
+
|
27
|
+
When running in Developer Mode, the RPM will also add a few pages to
|
28
|
+
your application that allow you to analyze this performance information. (Don't
|
29
|
+
worry - those pages are not added to your application's routes when you run in
|
30
|
+
production mode.)
|
31
|
+
|
32
|
+
To view this performance information, including detailed SQL statement analysis,
|
33
|
+
open '/newrelic' in your web application. For instance if you are running
|
34
|
+
mongrel or thin on port 3000, enter the following into your browser:
|
35
|
+
|
36
|
+
http://localhost:3000/newrelic
|
37
|
+
|
38
|
+
=== PRODUCTION MODE
|
39
|
+
|
40
|
+
To monitor your applications in production, create an account at
|
41
|
+
http://newrelic.com/get-RPM.htm
|
42
|
+
|
43
|
+
When your application runs in the production environment, the New Relic agent
|
44
|
+
runs in production mode. It connects to the New Relic RPM service and sends deep
|
45
|
+
performance data to the RPM service for your analysis. To view this data, login
|
46
|
+
to http://rpm.newrelic.com.
|
47
|
+
|
48
|
+
NOTE: You must have a valid account and license key to view this data online.
|
49
|
+
When you sign up for an account at www.newrelic.com, you will be provided with a
|
50
|
+
license key, as well as a default configuration file for New Relic RPM. You can
|
51
|
+
either paste your license key into your existing configuration file,
|
52
|
+
config/newrelic.yml, or you can replace that config file with the one included in
|
53
|
+
your welcome email.
|
54
|
+
|
55
|
+
== REQUIREMENTS:
|
56
|
+
|
57
|
+
Ruby 1.8.6
|
58
|
+
Rails 1.2.6 or above
|
59
|
+
Merb 1.0 or above
|
60
|
+
|
61
|
+
== RAILS PLUG-IN INSTALL:
|
62
|
+
|
63
|
+
script/plugin install http://newrelic.rubyforge.net/svn/newrelic_rpm
|
64
|
+
|
65
|
+
== GEM INSTALL:
|
66
|
+
|
67
|
+
sudo gem install newrelic_rpm
|
68
|
+
|
69
|
+
For Rails, edit environment.rb and add to the initalizer block:
|
70
|
+
|
71
|
+
config.gem "newrelic_rpm"
|
72
|
+
|
73
|
+
The Developer Mode is unavailable when using the gem on Rails versions prior to 2.0.
|
74
|
+
|
75
|
+
== MERB SUPPORT:
|
76
|
+
|
77
|
+
To monitor a merb app install the newrelic_rpm gem and add <code>dependency 'newrelic_rpm'</code>
|
78
|
+
to your init.rb file.
|
79
|
+
|
80
|
+
Developer Mode not currently available in merb.
|
81
|
+
|
82
|
+
== SUPPORT:
|
83
|
+
|
84
|
+
Reach out to us - and to fellow RPM users - on our support forum at
|
85
|
+
http://getsatisfaction.com/newrelic. We’ll share tips and tricks, answer all your
|
86
|
+
questions, and announce product updates. Operators are standing by.
|
87
|
+
|
88
|
+
For other support channels, see http://www.newrelic.com/support.
|
89
|
+
|
90
|
+
Thank you, and may your application scale to infinity plus one.
|
91
|
+
|
92
|
+
Lew Cirne, Founder and CEO
|
93
|
+
New Relic, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'lib/new_relic/version.rb'
|
4
|
+
|
5
|
+
GEM_NAME = "newrelic_rpm"
|
6
|
+
GEM_VERSION = NewRelic::VERSION::STRING
|
7
|
+
AUTHOR = "Bill Kayser"
|
8
|
+
EMAIL = "bkayser@newrelic.com"
|
9
|
+
HOMEPAGE = "http://www.newrelic.com"
|
10
|
+
SUMMARY = "New Relic Ruby Performance Monitoring Agent"
|
11
|
+
|
12
|
+
spec = Gem::Specification.new do |s|
|
13
|
+
s.rubyforge_project = 'newrelic'
|
14
|
+
s.name = GEM_NAME
|
15
|
+
s.version = GEM_VERSION
|
16
|
+
s.platform = Gem::Platform::RUBY
|
17
|
+
s.has_rdoc = true
|
18
|
+
s.extra_rdoc_files = ["README", "LICENSE"]
|
19
|
+
s.summary = SUMMARY
|
20
|
+
s.description = s.summary
|
21
|
+
s.author = AUTHOR
|
22
|
+
s.email = EMAIL
|
23
|
+
s.homepage = HOMEPAGE
|
24
|
+
s.require_path = 'lib'
|
25
|
+
s.files = %w(install.rb LICENSE README newrelic.yml Rakefile) + Dir.glob("{lib,recipes,test,ui}/**/*")
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
30
|
+
pkg.gem_spec = spec
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Create a gemspec file"
|
34
|
+
task :gemspec do
|
35
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
36
|
+
file.puts spec.to_ruby
|
37
|
+
end
|
38
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'ftools'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
# Install a newrelic.yml file into the local config directory.
|
5
|
+
# If no such directory exists, install it in ~/.newrelic.
|
6
|
+
#
|
7
|
+
# If a config file already exists, print a warning and exit.
|
8
|
+
#
|
9
|
+
if File.directory? "config"
|
10
|
+
dest_dir = "config"
|
11
|
+
else
|
12
|
+
dest_dir = File.join(ENV["HOME"],".newrelic") rescue nil
|
13
|
+
FileUtils.mkdir(dest_dir) if dest_dir
|
14
|
+
end
|
15
|
+
|
16
|
+
src_config_file = File.join(File.dirname(__FILE__),"newrelic.yml")
|
17
|
+
dest_config_file = File.join(dest_dir, "newrelic.yml") if dest_dir
|
18
|
+
|
19
|
+
if !dest_dir
|
20
|
+
STDERR.puts "Could not find a config or ~/.newrelic directory to locate the default newrelic.yml file"
|
21
|
+
elsif File::exists? dest_config_file
|
22
|
+
STDERR.puts "\nA config file already exists at #{dest_config_file}.\n"
|
23
|
+
else
|
24
|
+
generated_for_user = ""
|
25
|
+
license_key = "PASTE_YOUR_KEY_HERE"
|
26
|
+
yaml = ERB.new(File.read(src_config_file)).result(binding)
|
27
|
+
File.open( dest_config_file, 'w' ) do |out|
|
28
|
+
out.puts yaml
|
29
|
+
end
|
30
|
+
|
31
|
+
puts IO.read(File.join(File.dirname(__FILE__), 'README'))
|
32
|
+
puts "\n--------------------------------------------------------\n"
|
33
|
+
puts "Installing a default configuration file in #{dest_dir}."
|
34
|
+
puts "To monitor your application in production mode, you must enter a license key."
|
35
|
+
puts "See #{dest_config_file}"
|
36
|
+
puts "For a license key, sign up at http://rpm.newrelic.com/signup."
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'new_relic/version'
|
2
|
+
require 'new_relic/local_environment'
|
3
|
+
require 'new_relic/stats'
|
4
|
+
require 'new_relic/metric_spec'
|
5
|
+
require 'new_relic/metric_data'
|
6
|
+
require 'new_relic/transaction_analysis'
|
7
|
+
require 'new_relic/transaction_sample'
|
8
|
+
require 'new_relic/noticed_error'
|
9
|
+
|
10
|
+
require 'new_relic/agent/chained_call'
|
11
|
+
require 'new_relic/agent/agent'
|
12
|
+
require 'new_relic/agent/method_tracer'
|
13
|
+
require 'new_relic/agent/synchronize'
|
14
|
+
require 'new_relic/agent/worker_loop'
|
15
|
+
require 'new_relic/agent/stats_engine'
|
16
|
+
require 'new_relic/agent/collection_helper'
|
17
|
+
require 'new_relic/agent/transaction_sampler'
|
18
|
+
require 'new_relic/agent/error_collector'
|
19
|
+
|
20
|
+
require 'set'
|
21
|
+
require 'sync'
|
22
|
+
require 'thread'
|
23
|
+
|
24
|
+
module NewRelic::Agent
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,762 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'net/https'
|
3
|
+
require 'net/http'
|
4
|
+
require 'logger'
|
5
|
+
require 'singleton'
|
6
|
+
require 'zlib'
|
7
|
+
require 'stringio'
|
8
|
+
|
9
|
+
|
10
|
+
# This must be turned off before we ship
|
11
|
+
VALIDATE_BACKGROUND_THREAD_LOADING = false
|
12
|
+
|
13
|
+
# The NewRelic Agent collects performance data from ruby applications in realtime as the
|
14
|
+
# application runs, and periodically sends that data to the NewRelic server.
|
15
|
+
module NewRelic::Agent
|
16
|
+
# an exception that is thrown by the server if the agent license is invalid
|
17
|
+
class LicenseException < StandardError; end
|
18
|
+
|
19
|
+
# an exception that forces an agent to stop reporting until its mongrel is restarted
|
20
|
+
class ForceDisconnectException < StandardError; end
|
21
|
+
|
22
|
+
class IgnoreSilentlyException < StandardError; end
|
23
|
+
|
24
|
+
# Reserved for future use
|
25
|
+
class ServerError < StandardError; end
|
26
|
+
|
27
|
+
# add some convenience methods for easy access to the Agent singleton.
|
28
|
+
# the following static methods all point to the same Agent instance:
|
29
|
+
#
|
30
|
+
# NewRelic::Agent.agent
|
31
|
+
# NewRelic::Agent.instance
|
32
|
+
# NewRelic::Agent::Agent.instance
|
33
|
+
class << self
|
34
|
+
def agent
|
35
|
+
NewRelic::Agent::Agent.instance
|
36
|
+
end
|
37
|
+
|
38
|
+
alias instance agent
|
39
|
+
|
40
|
+
# Get or create a statistics gatherer that will aggregate numerical data
|
41
|
+
# under a metric name.
|
42
|
+
#
|
43
|
+
# metric_name should follow a slash separated path convention. Application
|
44
|
+
# specific metrics should begin with "Custom/".
|
45
|
+
#
|
46
|
+
# the statistical gatherer returned by get_stats accepts data
|
47
|
+
# via calls to add_data_point(value)
|
48
|
+
def get_stats(metric_name, use_scope=false)
|
49
|
+
agent.stats_engine.get_stats(metric_name, use_scope)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_stats_no_scope(metric_name)
|
53
|
+
agent.stats_engine.get_stats_no_scope(metric_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Call this to manually start the Agent in situations where the Agent does
|
58
|
+
# not auto-start.
|
59
|
+
# When the app environment loads, so does the Agent. However, the Agent will
|
60
|
+
# only connect to RPM if a web front-end is found. If you want to selectively monitor
|
61
|
+
# ruby processes that don't use web plugins, then call this method in your
|
62
|
+
# code and the Agent will fire up and start reporting to RPM.
|
63
|
+
#
|
64
|
+
# environment - the name of the environment. used for logging only
|
65
|
+
# port - the name of this instance. shows up in the RPM UI screens. can be any String
|
66
|
+
#
|
67
|
+
def manual_start(environment, identifier)
|
68
|
+
agent.manual_start(environment, identifier)
|
69
|
+
end
|
70
|
+
|
71
|
+
# This method sets the block sent to this method as a sql obfuscator.
|
72
|
+
# The block will be called with a single String SQL statement to obfuscate.
|
73
|
+
# The method must return the obfuscated String SQL.
|
74
|
+
# If chaining of obfuscators is required, use type = :before or :after
|
75
|
+
#
|
76
|
+
# type = :before, :replace, :after
|
77
|
+
#
|
78
|
+
# example:
|
79
|
+
# NewRelic::Agent.set_sql_obfuscator(:replace) do |sql|
|
80
|
+
# my_obfuscator(sql)
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
def set_sql_obfuscator(type = :replace, &block)
|
84
|
+
agent.set_sql_obfuscator type, &block
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# This method sets the state of sql recording in the transaction
|
89
|
+
# sampler feature. Within the given block, no sql will be recorded
|
90
|
+
#
|
91
|
+
# usage:
|
92
|
+
#
|
93
|
+
# NewRelic::Agent.disable_sql_recording do
|
94
|
+
# ...
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
def disable_sql_recording
|
98
|
+
state = agent.set_record_sql(false)
|
99
|
+
begin
|
100
|
+
yield
|
101
|
+
ensure
|
102
|
+
agent.set_record_sql(state)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# This method disables the recording of transaction traces in the given
|
107
|
+
# block.
|
108
|
+
def disable_transaction_tracing
|
109
|
+
state = agent.set_record_tt(false)
|
110
|
+
begin
|
111
|
+
yield
|
112
|
+
ensure
|
113
|
+
agent.set_record_tt(state)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# This method allows a filter to be applied to errors that RPM will track.
|
118
|
+
# The block should return the exception to track (which could be different from
|
119
|
+
# the original exception) or nil to ignore this exception
|
120
|
+
#
|
121
|
+
def ignore_error_filter(&block)
|
122
|
+
agent.error_collector.ignore_error_filter(&block)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Add parameters to the current transaction trace
|
126
|
+
#
|
127
|
+
def add_custom_parameters(params)
|
128
|
+
agent.add_custom_parameters(params)
|
129
|
+
end
|
130
|
+
|
131
|
+
alias add_request_parameters add_custom_parameters
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# Implementation default for the NewRelic Agent
|
136
|
+
class Agent
|
137
|
+
# Specifies the version of the agent's communication protocol
|
138
|
+
# with the NewRelic hosted site.
|
139
|
+
|
140
|
+
PROTOCOL_VERSION = 5
|
141
|
+
|
142
|
+
include Singleton
|
143
|
+
|
144
|
+
# Config object
|
145
|
+
attr_accessor :config
|
146
|
+
attr_reader :obfuscator
|
147
|
+
attr_reader :stats_engine
|
148
|
+
attr_reader :transaction_sampler
|
149
|
+
attr_reader :error_collector
|
150
|
+
attr_reader :worker_loop
|
151
|
+
attr_reader :license_key
|
152
|
+
attr_reader :remote_host
|
153
|
+
attr_reader :remote_port
|
154
|
+
attr_reader :record_sql
|
155
|
+
attr_reader :identifier
|
156
|
+
|
157
|
+
# This method is deprecated. Use start.
|
158
|
+
def manual_start(environment, identifier)
|
159
|
+
start(environment, identifier, true)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Start up the agent, which will connect to the newrelic server and start
|
163
|
+
# reporting performance information. Typically this is done from the
|
164
|
+
# environment configuration file.
|
165
|
+
# environment identifies the host environment, like mongrel, thin, or take.
|
166
|
+
# identifier is an identifier which uniquely identifies the process hosting
|
167
|
+
# the agent. It should be ideally something like a server port, like 3000,
|
168
|
+
# a handler thread name, or a script name. It should not be a PID because
|
169
|
+
# that will change
|
170
|
+
# from invocation to invocation. For something like rake, you could use
|
171
|
+
# the task name.
|
172
|
+
# Return false if the agent was not started
|
173
|
+
def start(environment, identifier, force=false)
|
174
|
+
|
175
|
+
if @started
|
176
|
+
log! "Agent Started Already!"
|
177
|
+
return
|
178
|
+
end
|
179
|
+
@environment = environment
|
180
|
+
@identifier = identifier && identifier.to_s
|
181
|
+
if @identifier
|
182
|
+
start_reporting(force)
|
183
|
+
return true
|
184
|
+
else
|
185
|
+
return false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# this method makes sure that the agent is running. it's important
|
190
|
+
# for passenger where processes are forked and the agent is dormant
|
191
|
+
#
|
192
|
+
def ensure_worker_thread_started
|
193
|
+
return unless @prod_mode_enabled && !@invalid_license
|
194
|
+
if @worker_loop.nil? || @worker_loop.pid != $$
|
195
|
+
launch_worker_thread
|
196
|
+
@stats_engine.spawn_sampler_thread
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# True if we have initialized and completed 'start_reporting'
|
201
|
+
def started?
|
202
|
+
@started
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
# Attempt a graceful shutdown of the agent.
|
207
|
+
def shutdown
|
208
|
+
return if !@started
|
209
|
+
if @worker_loop
|
210
|
+
@worker_loop.stop
|
211
|
+
|
212
|
+
log.debug "Starting Agent shutdown"
|
213
|
+
|
214
|
+
# if litespeed, then ignore all future SIGUSR1 - it's litespeed trying to shut us down
|
215
|
+
|
216
|
+
if @environment == :litespeed
|
217
|
+
Signal.trap("SIGUSR1", "IGNORE")
|
218
|
+
Signal.trap("SIGTERM", "IGNORE")
|
219
|
+
end
|
220
|
+
|
221
|
+
begin
|
222
|
+
graceful_disconnect
|
223
|
+
rescue => e
|
224
|
+
log.error e
|
225
|
+
log.error e.backtrace.join("\n")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
@started = nil
|
229
|
+
end
|
230
|
+
|
231
|
+
def start_transaction
|
232
|
+
Thread::current[:custom_params] = nil
|
233
|
+
@stats_engine.start_transaction
|
234
|
+
end
|
235
|
+
|
236
|
+
def end_transaction
|
237
|
+
Thread::current[:custom_params] = nil
|
238
|
+
@stats_engine.end_transaction
|
239
|
+
end
|
240
|
+
|
241
|
+
def set_record_sql(should_record)
|
242
|
+
prev = Thread::current[:record_sql]
|
243
|
+
Thread::current[:record_sql] = should_record
|
244
|
+
|
245
|
+
prev || true
|
246
|
+
end
|
247
|
+
|
248
|
+
def set_record_tt(should_record)
|
249
|
+
prev = Thread::current[:record_tt]
|
250
|
+
Thread::current[:record_tt] = should_record
|
251
|
+
|
252
|
+
prev || true
|
253
|
+
end
|
254
|
+
|
255
|
+
def add_custom_parameters(params)
|
256
|
+
p = Thread::current[:custom_params] || (Thread::current[:custom_params] = {})
|
257
|
+
|
258
|
+
p.merge!(params)
|
259
|
+
end
|
260
|
+
|
261
|
+
def custom_params
|
262
|
+
Thread::current[:custom_params] || {}
|
263
|
+
end
|
264
|
+
|
265
|
+
def set_sql_obfuscator(type, &block)
|
266
|
+
if type == :before
|
267
|
+
@obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
|
268
|
+
elsif type == :after
|
269
|
+
@obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
|
270
|
+
elsif type == :replace
|
271
|
+
@obfuscator = block
|
272
|
+
else
|
273
|
+
fail "unknown sql_obfuscator type #{type}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def instrument_app
|
278
|
+
return if @instrumented
|
279
|
+
|
280
|
+
@instrumented = true
|
281
|
+
|
282
|
+
# Instrumentation for the key code points inside rails for monitoring by NewRelic.
|
283
|
+
# note this file is loaded only if the newrelic agent is enabled (through config/newrelic.yml)
|
284
|
+
instrumentation_path = File.join(File.dirname(__FILE__), 'instrumentation')
|
285
|
+
instrumentation_files = [ ] <<
|
286
|
+
File.join(instrumentation_path, '*.rb') <<
|
287
|
+
File.join(instrumentation_path, config.app.to_s, '*.rb')
|
288
|
+
instrumentation_files.each do | pattern |
|
289
|
+
Dir.glob(pattern) do |file|
|
290
|
+
begin
|
291
|
+
log.debug "Processing instrumentation file '#{file}'"
|
292
|
+
require file
|
293
|
+
rescue => e
|
294
|
+
log.error "Error loading instrumentation file '#{file}': #{e}"
|
295
|
+
log.debug e.backtrace.join("\n")
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
log.debug "Finished instrumentation"
|
301
|
+
end
|
302
|
+
|
303
|
+
def log
|
304
|
+
setup_log unless @log
|
305
|
+
@log
|
306
|
+
end
|
307
|
+
|
308
|
+
def apdex_t
|
309
|
+
@apdex_t ||= (config['apdex_t'] || 2.0).to_f
|
310
|
+
end
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
# Connect to the server, and run the worker loop forever. Will not return.
|
315
|
+
def run_worker_loop
|
316
|
+
|
317
|
+
# connect to the server. this will keep retrying until successful or
|
318
|
+
# it determines the license is bad.
|
319
|
+
connect
|
320
|
+
|
321
|
+
# We may not be connected now but keep going for dev mode
|
322
|
+
if @connected
|
323
|
+
begin
|
324
|
+
# determine the reporting period (server based)
|
325
|
+
# note if the agent attempts to report more frequently than the specified
|
326
|
+
# report data, then it will be ignored.
|
327
|
+
|
328
|
+
log! "Reporting performance data every #{@report_period} seconds"
|
329
|
+
@worker_loop.add_task(@report_period) do
|
330
|
+
harvest_and_send_timeslice_data
|
331
|
+
end
|
332
|
+
|
333
|
+
if @should_send_samples && @use_transaction_sampler
|
334
|
+
@worker_loop.add_task(@report_period) do
|
335
|
+
harvest_and_send_slowest_sample
|
336
|
+
end
|
337
|
+
elsif !config.developer_mode?
|
338
|
+
# We still need the sampler for dev mode.
|
339
|
+
@transaction_sampler.disable
|
340
|
+
end
|
341
|
+
|
342
|
+
if @should_send_errors && @error_collector.enabled
|
343
|
+
@worker_loop.add_task(@report_period) do
|
344
|
+
harvest_and_send_errors
|
345
|
+
end
|
346
|
+
end
|
347
|
+
@worker_loop.run
|
348
|
+
rescue StandardError
|
349
|
+
@connected = false
|
350
|
+
raise
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def launch_worker_thread
|
356
|
+
if (@environment == :passenger && $0 =~ /ApplicationSpawner/)
|
357
|
+
log.info "Process is passenger spawner - don't connect to RPM service"
|
358
|
+
return
|
359
|
+
end
|
360
|
+
|
361
|
+
@worker_loop = WorkerLoop.new(log)
|
362
|
+
|
363
|
+
if VALIDATE_BACKGROUND_THREAD_LOADING
|
364
|
+
require 'new_relic/agent/patch_const_missing'
|
365
|
+
self.class.newrelic_enable_warning
|
366
|
+
end
|
367
|
+
|
368
|
+
@worker_thread = Thread.new do
|
369
|
+
begin
|
370
|
+
if VALIDATE_BACKGROUND_THREAD_LOADING
|
371
|
+
self.class.newrelic_set_agent_thread(Thread.current)
|
372
|
+
end
|
373
|
+
run_worker_loop
|
374
|
+
rescue IgnoreSilentlyException
|
375
|
+
log! "Unable to establish connection with the server. Run with log level set to debug for more information."
|
376
|
+
rescue StandardError => e
|
377
|
+
log! e
|
378
|
+
log! e.backtrace.join("\n")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# This code should be activated to check that no dependency loading is occuring in the background thread
|
383
|
+
# by stopping the foreground thread after the background thread is created. Turn on dependency loading logging
|
384
|
+
# and make sure that no loading occurs.
|
385
|
+
#
|
386
|
+
# log! "FINISHED AGENT INIT"
|
387
|
+
# while true
|
388
|
+
# sleep 1
|
389
|
+
# end
|
390
|
+
|
391
|
+
end
|
392
|
+
def start_reporting(force_enable=false)
|
393
|
+
@local_host = determine_host
|
394
|
+
|
395
|
+
setup_log
|
396
|
+
|
397
|
+
if @environment == :passenger
|
398
|
+
log.warn "Phusion Passenger has been detected. Some RPM memory statistics may have inaccuracies due to short process lifespans"
|
399
|
+
end
|
400
|
+
|
401
|
+
@started = true
|
402
|
+
|
403
|
+
@license_key = config.fetch('license_key', nil)
|
404
|
+
|
405
|
+
error_collector_config = config.fetch('error_collector', {})
|
406
|
+
|
407
|
+
@error_collector.enabled = error_collector_config.fetch('enabled', true)
|
408
|
+
@error_collector.capture_source = error_collector_config.fetch('capture_source', true)
|
409
|
+
|
410
|
+
log.info "Error collector is enabled in agent config" if @error_collector.enabled
|
411
|
+
|
412
|
+
ignore_errors = error_collector_config.fetch('ignore_errors', "")
|
413
|
+
ignore_errors = ignore_errors.split(",")
|
414
|
+
ignore_errors.each { |error| error.strip! }
|
415
|
+
|
416
|
+
@error_collector.ignore(ignore_errors)
|
417
|
+
|
418
|
+
|
419
|
+
@capture_params = config.fetch('capture_params', false)
|
420
|
+
|
421
|
+
sampler_config = config.fetch('transaction_tracer', {})
|
422
|
+
|
423
|
+
@use_transaction_sampler = sampler_config.fetch('enabled', false)
|
424
|
+
@record_sql = (sampler_config.fetch('record_sql', 'obfuscated') || 'off').intern
|
425
|
+
@slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', '2.0').to_f
|
426
|
+
@explain_threshold = sampler_config.fetch('explain_threshold', '0.5').to_f
|
427
|
+
@explain_enabled = sampler_config.fetch('explain_enabled', true)
|
428
|
+
@stack_trace_threshold = sampler_config.fetch('stack_trace_threshold', '0.500').to_f
|
429
|
+
|
430
|
+
log.info "Transaction tracing is enabled in agent config" if @use_transaction_sampler
|
431
|
+
log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
|
432
|
+
|
433
|
+
@use_ssl = config.fetch('ssl', false)
|
434
|
+
default_port = @use_ssl ? 443 : 80
|
435
|
+
|
436
|
+
@remote_host = config.fetch('host', 'collector.newrelic.com')
|
437
|
+
@remote_port = config.fetch('port', default_port)
|
438
|
+
|
439
|
+
@proxy_host = config.fetch('proxy_host', nil)
|
440
|
+
@proxy_port = config.fetch('proxy_port', nil)
|
441
|
+
@proxy_user = config.fetch('proxy_user', nil)
|
442
|
+
@proxy_pass = config.fetch('proxy_pass', nil)
|
443
|
+
|
444
|
+
@prod_mode_enabled = force_enable || config['enabled']
|
445
|
+
|
446
|
+
# Initialize transaction sampler
|
447
|
+
TransactionSampler.capture_params = @capture_params
|
448
|
+
@transaction_sampler.stack_trace_threshold = @stack_trace_threshold
|
449
|
+
@error_collector.capture_params = @capture_params
|
450
|
+
|
451
|
+
|
452
|
+
# make sure the license key exists and is likely to be really a license key
|
453
|
+
# by checking it's string length (license keys are 40 character strings.)
|
454
|
+
if @prod_mode_enabled && (!@license_key || @license_key.length != 40)
|
455
|
+
log! "No license key found. Please edit your newrelic.yml file and insert your license key"
|
456
|
+
return
|
457
|
+
end
|
458
|
+
|
459
|
+
instrument_app
|
460
|
+
|
461
|
+
if @prod_mode_enabled
|
462
|
+
load_samplers
|
463
|
+
launch_worker_thread
|
464
|
+
# When the VM shuts down, attempt to send a message to the server that
|
465
|
+
# this agent run is stopping, assuming it has successfully connected
|
466
|
+
at_exit { shutdown }
|
467
|
+
end
|
468
|
+
|
469
|
+
log.debug "Finished starting reporting"
|
470
|
+
end
|
471
|
+
def initialize
|
472
|
+
@connected = false
|
473
|
+
@launch_time = Time.now
|
474
|
+
|
475
|
+
@metric_ids = {}
|
476
|
+
@environment = :unknown
|
477
|
+
|
478
|
+
@config = NewRelic::Config.instance
|
479
|
+
|
480
|
+
@stats_engine = NewRelic::Agent::StatsEngine.new
|
481
|
+
@transaction_sampler = NewRelic::Agent::TransactionSampler.new(self)
|
482
|
+
@error_collector = NewRelic::Agent::ErrorCollector.new(self)
|
483
|
+
|
484
|
+
@request_timeout = 15 * 60
|
485
|
+
|
486
|
+
@invalid_license = false
|
487
|
+
|
488
|
+
@last_harvest_time = Time.now
|
489
|
+
end
|
490
|
+
|
491
|
+
def setup_log
|
492
|
+
@log = config.setup_log(identifier)
|
493
|
+
log.info "Runtime environment: #{@environment.to_s}"
|
494
|
+
end
|
495
|
+
|
496
|
+
# Connect to the server and validate the license.
|
497
|
+
# If successful, @connected has true when finished.
|
498
|
+
# If not successful, you can keep calling this.
|
499
|
+
# Return false if we could not establish a connection with the
|
500
|
+
# server and we should not retry, such as if there's
|
501
|
+
# a bad license key.
|
502
|
+
def connect
|
503
|
+
# wait a few seconds for the web server to boot, necessary in development
|
504
|
+
connect_retry_period = 5
|
505
|
+
connect_attempts = 0
|
506
|
+
|
507
|
+
begin
|
508
|
+
sleep connect_retry_period.to_i
|
509
|
+
@agent_id = invoke_remote :launch,
|
510
|
+
@local_host,
|
511
|
+
@identifier,
|
512
|
+
determine_home_directory,
|
513
|
+
$$,
|
514
|
+
@launch_time.to_f,
|
515
|
+
NewRelic::VERSION::STRING,
|
516
|
+
config.app_config_info,
|
517
|
+
config['app_name'],
|
518
|
+
config.settings
|
519
|
+
@report_period = invoke_remote :get_data_report_period, @agent_id
|
520
|
+
|
521
|
+
log! "Connected to NewRelic Service at #{@remote_host}:#{@remote_port}."
|
522
|
+
log.debug "Agent ID = #{@agent_id}."
|
523
|
+
|
524
|
+
# Ask the server for permission to send transaction samples. determined by subscription license.
|
525
|
+
@should_send_samples = invoke_remote :should_collect_samples, @agent_id
|
526
|
+
|
527
|
+
# Ask for mermission to collect error data
|
528
|
+
@should_send_errors = invoke_remote :should_collect_errors, @agent_id
|
529
|
+
|
530
|
+
log.info "Transaction traces will be sent to the RPM service" if @use_transaction_sampler && @should_send_samples
|
531
|
+
log.info "Errors will be sent to the RPM service" if @error_collector.enabled && @should_send_errors
|
532
|
+
|
533
|
+
@connected = true
|
534
|
+
|
535
|
+
rescue LicenseException => e
|
536
|
+
log! e.message, :error
|
537
|
+
log! "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
|
538
|
+
@invalid_license = true
|
539
|
+
return false
|
540
|
+
|
541
|
+
rescue Timeout::Error, StandardError => e
|
542
|
+
log.info "Unable to establish connection with New Relic RPM Service at #{@remote_host}:#{@remote_port}"
|
543
|
+
unless e.instance_of? IgnoreSilentlyException
|
544
|
+
log.error e.message
|
545
|
+
log.debug e.backtrace.join("\n")
|
546
|
+
end
|
547
|
+
# retry logic
|
548
|
+
connect_attempts += 1
|
549
|
+
case connect_attempts
|
550
|
+
when 1..5
|
551
|
+
connect_retry_period, period_msg = 5, nil
|
552
|
+
when 6..10 then
|
553
|
+
connect_retry_period, period_msg = 30, nil
|
554
|
+
when 11..20 then
|
555
|
+
connect_retry_period, period_msg = 1.minutes, "1 minute"
|
556
|
+
else
|
557
|
+
connect_retry_period, period_msg = 10.minutes, "10 minutes"
|
558
|
+
end
|
559
|
+
log.info "Will re-attempt in #{period_msg}" if period_msg
|
560
|
+
retry
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
def load_samplers
|
565
|
+
sampler_files = File.join(File.dirname(__FILE__), 'samplers', '*.rb')
|
566
|
+
Dir.glob(sampler_files) do |file|
|
567
|
+
begin
|
568
|
+
require file
|
569
|
+
rescue => e
|
570
|
+
log.error "Error loading sampler '#{file}': #{e}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
def determine_host
|
576
|
+
Socket.gethostname
|
577
|
+
end
|
578
|
+
|
579
|
+
def determine_home_directory
|
580
|
+
config.root
|
581
|
+
end
|
582
|
+
|
583
|
+
def harvest_and_send_timeslice_data
|
584
|
+
|
585
|
+
NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.harvest_busy
|
586
|
+
|
587
|
+
now = Time.now
|
588
|
+
|
589
|
+
# Fixme: remove the harvest thread tracking
|
590
|
+
@harvest_thread ||= Thread.current
|
591
|
+
|
592
|
+
if @harvest_thread != Thread.current
|
593
|
+
log! "ERROR - two harvest threads are running (current=#{Thread.current}, havest=#{@harvest_thread}"
|
594
|
+
@harvest_thread = Thread.current
|
595
|
+
end
|
596
|
+
|
597
|
+
# Fixme: remove this check
|
598
|
+
log! "Agent sending data too frequently - #{now - @last_harvest_time} seconds" if (now.to_f - @last_harvest_time.to_f) < 45
|
599
|
+
|
600
|
+
@unsent_timeslice_data ||= {}
|
601
|
+
@unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
|
602
|
+
|
603
|
+
begin
|
604
|
+
metric_ids = invoke_remote(:metric_data, @agent_id,
|
605
|
+
@last_harvest_time.to_f,
|
606
|
+
now.to_f,
|
607
|
+
@unsent_timeslice_data.values)
|
608
|
+
|
609
|
+
rescue Timeout::Error
|
610
|
+
# assume that the data was received. chances are that it was
|
611
|
+
metric_ids = nil
|
612
|
+
end
|
613
|
+
|
614
|
+
|
615
|
+
@metric_ids.merge! metric_ids if metric_ids
|
616
|
+
|
617
|
+
log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
|
618
|
+
|
619
|
+
# if we successfully invoked this web service, then clear the unsent message cache.
|
620
|
+
@unsent_timeslice_data = {}
|
621
|
+
@last_harvest_time = now
|
622
|
+
|
623
|
+
# handle_messages
|
624
|
+
|
625
|
+
# note - exceptions are logged in invoke_remote. If an exception is encountered here,
|
626
|
+
# then the metric data is downsampled for another timeslices
|
627
|
+
end
|
628
|
+
|
629
|
+
def harvest_and_send_slowest_sample
|
630
|
+
@slowest_sample = @transaction_sampler.harvest_slowest_sample(@slowest_sample)
|
631
|
+
|
632
|
+
if @slowest_sample && @slowest_sample.duration > @slowest_transaction_threshold
|
633
|
+
now = Time.now
|
634
|
+
log.debug "Sending slowest sample: #{@slowest_sample.params[:path]}, #{@slowest_sample.duration.round_to(2)}s (explain=#{@explain_enabled})" if @slowest_sample
|
635
|
+
|
636
|
+
# take the slowest sample, and prepare it for sending across the wire. This includes
|
637
|
+
# gathering SQL explanations, stripping out stack traces, and normalizing SQL.
|
638
|
+
# note that we explain only the sql statements whose segments' execution times exceed
|
639
|
+
# our threshold (to avoid unnecessary overhead of running explains on fast queries.)
|
640
|
+
sample = @slowest_sample.prepare_to_send(:explain_sql => @explain_threshold, :record_sql => @record_sql, :keep_backtraces => true, :explain_enabled => @explain_enabled)
|
641
|
+
|
642
|
+
invoke_remote :transaction_sample_data, @agent_id, sample
|
643
|
+
|
644
|
+
log.debug "#{now}: sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
|
645
|
+
end
|
646
|
+
|
647
|
+
# if we succeed sending this sample, then we don't need to keep the slowest sample
|
648
|
+
# around - it has been sent already and we can collect the next one
|
649
|
+
@slowest_sample = nil
|
650
|
+
|
651
|
+
# note - exceptions are logged in invoke_remote. If an exception is encountered here,
|
652
|
+
# then the slowest sample of is determined of the entire period since the last
|
653
|
+
# reported sample.
|
654
|
+
end
|
655
|
+
|
656
|
+
def harvest_and_send_errors
|
657
|
+
@unsent_errors = @error_collector.harvest_errors(@unsent_errors)
|
658
|
+
if @unsent_errors && @unsent_errors.length > 0
|
659
|
+
log.debug "Sending #{@unsent_errors.length} errors"
|
660
|
+
|
661
|
+
invoke_remote :error_data, @agent_id, @unsent_errors
|
662
|
+
|
663
|
+
# if the remote invocation fails, then we never clear @unsent_errors,
|
664
|
+
# and therefore we can re-attempt to send on the next heartbeat. Note
|
665
|
+
# the error collector maxes out at 20 instances to prevent leakage
|
666
|
+
@unsent_errors = []
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
# send a message via post
|
671
|
+
def invoke_remote(method, *args)
|
672
|
+
# we currently optimize for CPU here since we get roughly a 10x reduction in
|
673
|
+
# message size with this, and CPU overhead is at a premium. If we wanted
|
674
|
+
# to go for higher compression instead, we could use Zlib::BEST_COMPRESSION and
|
675
|
+
# pay a little more CPU.
|
676
|
+
post_data = Zlib::Deflate.deflate(Marshal.dump(args), Zlib::BEST_SPEED)
|
677
|
+
|
678
|
+
# Proxy returns regular HTTP if @proxy_host is nil (the default)
|
679
|
+
http = Net::HTTP::Proxy(@proxy_host, @proxy_port, @proxy_user, @proxy_pass).new(@remote_host, @remote_port.to_i)
|
680
|
+
if @use_ssl
|
681
|
+
http.use_ssl = true
|
682
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
683
|
+
end
|
684
|
+
|
685
|
+
http.read_timeout = @request_timeout
|
686
|
+
|
687
|
+
# params = {:method => method, :license_key => license_key, :protocol_version => PROTOCOL_VERSION }
|
688
|
+
# uri = "/agent_listener/invoke_raw_method?#{params.to_query}"
|
689
|
+
uri = "/agent_listener/invoke_raw_method?method=#{method}&license_key=#{license_key}&protocol_version=#{PROTOCOL_VERSION}"
|
690
|
+
uri += "&run_id=#{@agent_id}" if @agent_id
|
691
|
+
|
692
|
+
request = Net::HTTP::Post.new(uri, 'ACCEPT-ENCODING' => 'gzip')
|
693
|
+
request.content_type = "application/octet-stream"
|
694
|
+
request.body = post_data
|
695
|
+
|
696
|
+
log.debug "#{uri}"
|
697
|
+
|
698
|
+
response = http.request(request)
|
699
|
+
|
700
|
+
if response.is_a? Net::HTTPSuccess
|
701
|
+
body = nil
|
702
|
+
if response['content-encoding'] == 'gzip'
|
703
|
+
log.debug "Decompressing return value"
|
704
|
+
i = Zlib::GzipReader.new(StringIO.new(response.body))
|
705
|
+
body = i.read
|
706
|
+
else
|
707
|
+
log.debug "Uncompressed content returned"
|
708
|
+
body = response.body
|
709
|
+
end
|
710
|
+
return_value = Marshal.load(body)
|
711
|
+
if return_value.is_a? Exception
|
712
|
+
raise return_value
|
713
|
+
else
|
714
|
+
return return_value
|
715
|
+
end
|
716
|
+
else
|
717
|
+
log.debug "Unexpected response from server: #{response.code}: #{response.message}"
|
718
|
+
raise IgnoreSilentlyException
|
719
|
+
end
|
720
|
+
rescue ForceDisconnectException => e
|
721
|
+
log! "RPM forced this agent to disconnect", :error
|
722
|
+
log! e.message, :error
|
723
|
+
log! "Restart this process to resume RPM's agent communication with NewRelic.com"
|
724
|
+
# when a disconnect is requested, stop the current thread, which is the worker thread that
|
725
|
+
# gathers data and talks to the server.
|
726
|
+
@connected = false
|
727
|
+
Thread.exit
|
728
|
+
rescue SystemCallError, SocketError => e
|
729
|
+
# These include Errno connection errors
|
730
|
+
log.debug "Recoverable error connecting to the server: #{e}"
|
731
|
+
raise IgnoreSilentlyException
|
732
|
+
end
|
733
|
+
|
734
|
+
# send the given message to STDERR as well as the agent log, so that it shows
|
735
|
+
# up in the console. This should be used for important informational messages at boot
|
736
|
+
def log!(msg, level = :info)
|
737
|
+
# only log to stderr when we are running as a mongrel process, so it doesn't
|
738
|
+
# muck with daemons and the like.
|
739
|
+
config.log!(msg, level)
|
740
|
+
end
|
741
|
+
|
742
|
+
def graceful_disconnect
|
743
|
+
if @connected && !(remote_host == "localhost" && @identifier == '3000')
|
744
|
+
begin
|
745
|
+
log.debug "Sending graceful shutdown message to #{remote_host}:#{remote_port}"
|
746
|
+
|
747
|
+
@request_timeout = 5
|
748
|
+
|
749
|
+
log.debug "Sending RPM service agent run shutdown message"
|
750
|
+
invoke_remote :shutdown, @agent_id, Time.now.to_f
|
751
|
+
|
752
|
+
log.debug "Graceful shutdown complete"
|
753
|
+
|
754
|
+
rescue Timeout::Error, StandardError => e
|
755
|
+
end
|
756
|
+
else
|
757
|
+
log.debug "Bypassing graceful shutdown - agent in development mode"
|
758
|
+
end
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
end
|