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.

Files changed (107) hide show
  1. data/LICENSE +37 -0
  2. data/README +93 -0
  3. data/Rakefile +38 -0
  4. data/install.rb +37 -0
  5. data/lib/new_relic/agent.rb +26 -0
  6. data/lib/new_relic/agent/agent.rb +762 -0
  7. data/lib/new_relic/agent/chained_call.rb +13 -0
  8. data/lib/new_relic/agent/collection_helper.rb +81 -0
  9. data/lib/new_relic/agent/error_collector.rb +105 -0
  10. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +95 -0
  11. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +151 -0
  12. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  13. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +105 -0
  14. data/lib/new_relic/agent/instrumentation/memcache.rb +18 -0
  15. data/lib/new_relic/agent/instrumentation/merb/controller.rb +17 -0
  16. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +15 -0
  17. data/lib/new_relic/agent/instrumentation/merb/errors.rb +6 -0
  18. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +35 -0
  19. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  20. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +30 -0
  21. data/lib/new_relic/agent/instrumentation/rails/errors.rb +23 -0
  22. data/lib/new_relic/agent/instrumentation/rails/rails.rb +6 -0
  23. data/lib/new_relic/agent/method_tracer.rb +171 -0
  24. data/lib/new_relic/agent/patch_const_missing.rb +31 -0
  25. data/lib/new_relic/agent/samplers/cpu.rb +29 -0
  26. data/lib/new_relic/agent/samplers/memory.rb +55 -0
  27. data/lib/new_relic/agent/samplers/mongrel.rb +26 -0
  28. data/lib/new_relic/agent/stats_engine.rb +241 -0
  29. data/lib/new_relic/agent/synchronize.rb +40 -0
  30. data/lib/new_relic/agent/transaction_sampler.rb +281 -0
  31. data/lib/new_relic/agent/worker_loop.rb +128 -0
  32. data/lib/new_relic/api/deployments.rb +92 -0
  33. data/lib/new_relic/config.rb +194 -0
  34. data/lib/new_relic/config/merb.rb +35 -0
  35. data/lib/new_relic/config/rails.rb +113 -0
  36. data/lib/new_relic/config/ruby.rb +9 -0
  37. data/lib/new_relic/local_environment.rb +108 -0
  38. data/lib/new_relic/merbtasks.rb +6 -0
  39. data/lib/new_relic/metric_data.rb +26 -0
  40. data/lib/new_relic/metric_spec.rb +39 -0
  41. data/lib/new_relic/metrics.rb +7 -0
  42. data/lib/new_relic/noticed_error.rb +21 -0
  43. data/lib/new_relic/shim_agent.rb +95 -0
  44. data/lib/new_relic/stats.rb +359 -0
  45. data/lib/new_relic/transaction_analysis.rb +122 -0
  46. data/lib/new_relic/transaction_sample.rb +499 -0
  47. data/lib/new_relic/version.rb +111 -0
  48. data/lib/new_relic_api.rb +275 -0
  49. data/lib/newrelic_rpm.rb +27 -0
  50. data/lib/tasks/agent_tests.rake +14 -0
  51. data/lib/tasks/all.rb +4 -0
  52. data/lib/tasks/install.rake +7 -0
  53. data/newrelic.yml +137 -0
  54. data/recipes/newrelic.rb +46 -0
  55. data/test/config/newrelic.yml +26 -0
  56. data/test/config/test_config.rb +9 -0
  57. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  58. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  59. data/test/new_relic/agent/model_fixture.rb +17 -0
  60. data/test/new_relic/agent/tc_active_record.rb +91 -0
  61. data/test/new_relic/agent/tc_agent.rb +112 -0
  62. data/test/new_relic/agent/tc_collection_helper.rb +104 -0
  63. data/test/new_relic/agent/tc_controller.rb +98 -0
  64. data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +52 -0
  65. data/test/new_relic/agent/tc_error_collector.rb +127 -0
  66. data/test/new_relic/agent/tc_method_tracer.rb +306 -0
  67. data/test/new_relic/agent/tc_stats_engine.rb +218 -0
  68. data/test/new_relic/agent/tc_synchronize.rb +37 -0
  69. data/test/new_relic/agent/tc_transaction_sample.rb +175 -0
  70. data/test/new_relic/agent/tc_transaction_sample_builder.rb +200 -0
  71. data/test/new_relic/agent/tc_transaction_sampler.rb +305 -0
  72. data/test/new_relic/agent/tc_worker_loop.rb +101 -0
  73. data/test/new_relic/agent/testable_agent.rb +13 -0
  74. data/test/new_relic/tc_config.rb +36 -0
  75. data/test/new_relic/tc_deployments_api.rb +37 -0
  76. data/test/new_relic/tc_environment.rb +94 -0
  77. data/test/new_relic/tc_metric_spec.rb +150 -0
  78. data/test/new_relic/tc_shim_agent.rb +9 -0
  79. data/test/new_relic/tc_stats.rb +141 -0
  80. data/test/test_helper.rb +39 -0
  81. data/test/ui/tc_newrelic_helper.rb +44 -0
  82. data/ui/controllers/newrelic_controller.rb +200 -0
  83. data/ui/helpers/google_pie_chart.rb +55 -0
  84. data/ui/helpers/newrelic_helper.rb +286 -0
  85. data/ui/views/layouts/newrelic_default.rhtml +49 -0
  86. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  87. data/ui/views/newrelic/_sample.rhtml +12 -0
  88. data/ui/views/newrelic/_segment.rhtml +28 -0
  89. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  90. data/ui/views/newrelic/_show_sample_detail.rhtml +22 -0
  91. data/ui/views/newrelic/_show_sample_sql.rhtml +19 -0
  92. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  93. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  94. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  95. data/ui/views/newrelic/_table.rhtml +12 -0
  96. data/ui/views/newrelic/explain_sql.rhtml +45 -0
  97. data/ui/views/newrelic/images/arrow-close.png +0 -0
  98. data/ui/views/newrelic/images/arrow-open.png +0 -0
  99. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  100. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  101. data/ui/views/newrelic/index.rhtml +37 -0
  102. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  103. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  104. data/ui/views/newrelic/show_sample.rhtml +62 -0
  105. data/ui/views/newrelic/show_source.rhtml +3 -0
  106. data/ui/views/newrelic/stylesheets/style.css +394 -0
  107. 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.
@@ -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
@@ -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