genki-newrelic_rpm 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/CHANGELOG +316 -0
  2. data/LICENSE +37 -0
  3. data/Manifest +156 -0
  4. data/README.md +138 -0
  5. data/Rakefile +22 -0
  6. data/bin/mongrel_rpm +33 -0
  7. data/bin/newrelic_cmd +4 -0
  8. data/cert/cacert.pem +34 -0
  9. data/genki-newrelic_rpm.gemspec +32 -0
  10. data/init.rb +38 -0
  11. data/install.rb +37 -0
  12. data/lib/new_relic/agent.rb +280 -0
  13. data/lib/new_relic/agent/agent.rb +627 -0
  14. data/lib/new_relic/agent/chained_call.rb +13 -0
  15. data/lib/new_relic/agent/collection_helper.rb +61 -0
  16. data/lib/new_relic/agent/error_collector.rb +125 -0
  17. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  18. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +83 -0
  19. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  20. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +368 -0
  21. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  22. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +132 -0
  23. data/lib/new_relic/agent/instrumentation/memcache.rb +21 -0
  24. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  25. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +13 -0
  26. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  27. data/lib/new_relic/agent/instrumentation/net.rb +12 -0
  28. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  29. data/lib/new_relic/agent/instrumentation/rack.rb +77 -0
  30. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  31. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  32. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +38 -0
  33. data/lib/new_relic/agent/instrumentation/rails/errors.rb +27 -0
  34. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  35. data/lib/new_relic/agent/method_tracer.rb +277 -0
  36. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  37. data/lib/new_relic/agent/sampler.rb +12 -0
  38. data/lib/new_relic/agent/samplers/cpu_sampler.rb +49 -0
  39. data/lib/new_relic/agent/samplers/memory_sampler.rb +137 -0
  40. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  41. data/lib/new_relic/agent/shim_agent.rb +21 -0
  42. data/lib/new_relic/agent/stats_engine.rb +24 -0
  43. data/lib/new_relic/agent/stats_engine/metric_stats.rb +111 -0
  44. data/lib/new_relic/agent/stats_engine/samplers.rb +71 -0
  45. data/lib/new_relic/agent/stats_engine/transactions.rb +155 -0
  46. data/lib/new_relic/agent/transaction_sampler.rb +319 -0
  47. data/lib/new_relic/agent/worker_loop.rb +118 -0
  48. data/lib/new_relic/commands/deployments.rb +145 -0
  49. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  50. data/lib/new_relic/control.rb +436 -0
  51. data/lib/new_relic/control/external.rb +13 -0
  52. data/lib/new_relic/control/merb.rb +22 -0
  53. data/lib/new_relic/control/rails.rb +143 -0
  54. data/lib/new_relic/control/ruby.rb +34 -0
  55. data/lib/new_relic/control/sinatra.rb +14 -0
  56. data/lib/new_relic/histogram.rb +89 -0
  57. data/lib/new_relic/local_environment.rb +285 -0
  58. data/lib/new_relic/merbtasks.rb +6 -0
  59. data/lib/new_relic/metric_data.rb +44 -0
  60. data/lib/new_relic/metric_parser.rb +120 -0
  61. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  62. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  63. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  64. data/lib/new_relic/metric_parser/controller.rb +54 -0
  65. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  66. data/lib/new_relic/metric_parser/errors.rb +6 -0
  67. data/lib/new_relic/metric_parser/external.rb +50 -0
  68. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  69. data/lib/new_relic/metric_parser/view.rb +61 -0
  70. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  71. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  72. data/lib/new_relic/metric_spec.rb +52 -0
  73. data/lib/new_relic/metrics.rb +7 -0
  74. data/lib/new_relic/noticed_error.rb +25 -0
  75. data/lib/new_relic/rack/metric_app.rb +56 -0
  76. data/lib/new_relic/rack/newrelic.ru +25 -0
  77. data/lib/new_relic/rack/newrelic.yml +25 -0
  78. data/lib/new_relic/rack_app.rb +5 -0
  79. data/lib/new_relic/recipes.rb +82 -0
  80. data/lib/new_relic/stats.rb +360 -0
  81. data/lib/new_relic/transaction_analysis.rb +121 -0
  82. data/lib/new_relic/transaction_sample.rb +583 -0
  83. data/lib/new_relic/version.rb +54 -0
  84. data/lib/new_relic_api.rb +315 -0
  85. data/lib/newrelic_rpm.rb +40 -0
  86. data/lib/tasks/all.rb +4 -0
  87. data/lib/tasks/install.rake +7 -0
  88. data/lib/tasks/tests.rake +13 -0
  89. data/newrelic.yml +214 -0
  90. data/recipes/newrelic.rb +6 -0
  91. data/test/active_record_fixtures.rb +55 -0
  92. data/test/config/newrelic.yml +46 -0
  93. data/test/config/test_control.rb +39 -0
  94. data/test/new_relic/agent/active_record_instrumentation_test.rb +234 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +107 -0
  96. data/test/new_relic/agent/agent_test.rb +117 -0
  97. data/test/new_relic/agent/agent_test_controller.rb +44 -0
  98. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  99. data/test/new_relic/agent/collection_helper_test.rb +118 -0
  100. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +76 -0
  101. data/test/new_relic/agent/error_collector_test.rb +155 -0
  102. data/test/new_relic/agent/method_tracer_test.rb +335 -0
  103. data/test/new_relic/agent/metric_data_test.rb +56 -0
  104. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  105. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  106. data/test/new_relic/agent/net_instrumentation_test.rb +51 -0
  107. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  108. data/test/new_relic/agent/stats_engine/samplers_test.rb +78 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +177 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +67 -0
  111. data/test/new_relic/agent/testable_agent.rb +13 -0
  112. data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
  113. data/test/new_relic/agent/transaction_sample_test.rb +146 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +387 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +94 -0
  117. data/test/new_relic/deployments_api_test.rb +68 -0
  118. data/test/new_relic/environment_test.rb +75 -0
  119. data/test/new_relic/metric_parser_test.rb +172 -0
  120. data/test/new_relic/metric_spec_test.rb +177 -0
  121. data/test/new_relic/shim_agent_test.rb +9 -0
  122. data/test/new_relic/stats_test.rb +291 -0
  123. data/test/new_relic/version_number_test.rb +74 -0
  124. data/test/test_helper.rb +38 -0
  125. data/test/ui/newrelic_controller_test.rb +14 -0
  126. data/test/ui/newrelic_helper_test.rb +53 -0
  127. data/ui/controllers/newrelic_controller.rb +214 -0
  128. data/ui/helpers/google_pie_chart.rb +55 -0
  129. data/ui/helpers/newrelic_helper.rb +314 -0
  130. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  131. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  132. data/ui/views/newrelic/_sample.rhtml +15 -0
  133. data/ui/views/newrelic/_segment.rhtml +28 -0
  134. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  135. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  136. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  137. data/ui/views/newrelic/_show_sample_sql.rhtml +20 -0
  138. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  139. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  140. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  141. data/ui/views/newrelic/_table.rhtml +12 -0
  142. data/ui/views/newrelic/explain_sql.rhtml +42 -0
  143. data/ui/views/newrelic/images/arrow-close.png +0 -0
  144. data/ui/views/newrelic/images/arrow-open.png +0 -0
  145. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  146. data/ui/views/newrelic/images/file_icon.png +0 -0
  147. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  148. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  149. data/ui/views/newrelic/images/textmate.png +0 -0
  150. data/ui/views/newrelic/index.rhtml +45 -0
  151. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  152. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  153. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  154. data/ui/views/newrelic/show_sample.rhtml +77 -0
  155. data/ui/views/newrelic/show_source.rhtml +3 -0
  156. data/ui/views/newrelic/stylesheets/style.css +433 -0
  157. data/ui/views/newrelic/threads.rhtml +52 -0
  158. metadata +327 -0
@@ -0,0 +1,627 @@
1
+ require 'socket'
2
+ require 'net/https'
3
+ require 'net/http'
4
+ require 'logger'
5
+ require 'zlib'
6
+ require 'stringio'
7
+
8
+ # The NewRelic Agent collects performance data from ruby applications
9
+ # in realtime as the application runs, and periodically sends that
10
+ # data to the NewRelic server.
11
+ module NewRelic::Agent
12
+
13
+ # The Agent is a singleton that is instantiated when the plugin is
14
+ # activated.
15
+ class Agent
16
+
17
+ # Specifies the version of the agent's communication protocol with
18
+ # the NewRelic hosted site.
19
+
20
+ PROTOCOL_VERSION = 6
21
+
22
+ attr_reader :obfuscator
23
+ attr_reader :stats_engine
24
+ attr_reader :transaction_sampler
25
+ attr_reader :error_collector
26
+ attr_reader :task_loop
27
+ attr_reader :record_sql
28
+ attr_reader :histogram
29
+
30
+ # Should only be called by NewRelic::Control
31
+ def self.instance
32
+ @instance ||= self.new
33
+ end
34
+ # This method is deprecated. Use NewRelic::Agent.manual_start
35
+ def manual_start(ignored=nil, also_ignored=nil)
36
+ raise "This method no longer supported. Instead use the class method NewRelic::Agent.manual_start"
37
+ end
38
+
39
+ # this method makes sure that the agent is running. it's important
40
+ # for passenger where processes are forked and the agent is
41
+ # dormant
42
+ #
43
+ def ensure_worker_thread_started
44
+ return unless control.agent_enabled? && control.monitor_mode? && !@invalid_license
45
+ if !running?
46
+ launch_worker_thread
47
+ @stats_engine.spawn_sampler_thread
48
+ end
49
+ end
50
+
51
+ # True if the worker thread has been started. Doesn't necessarily
52
+ # mean we are connected
53
+ def running?
54
+ control.agent_enabled? && control.monitor_mode? && @task_loop && @task_loop.pid == $$
55
+ end
56
+
57
+ # True if we have initialized and completed 'start'
58
+ def started?
59
+ @started
60
+ end
61
+
62
+ # Attempt a graceful shutdown of the agent.
63
+ def shutdown
64
+ return if not started?
65
+ if @task_loop
66
+ @task_loop.stop
67
+
68
+ log.debug "Starting Agent shutdown"
69
+
70
+ # if litespeed, then ignore all future SIGUSR1 - it's
71
+ # litespeed trying to shut us down
72
+
73
+ if control.dispatcher == :litespeed
74
+ Signal.trap("SIGUSR1", "IGNORE")
75
+ Signal.trap("SIGTERM", "IGNORE")
76
+ end
77
+
78
+ begin
79
+ graceful_disconnect
80
+ rescue => e
81
+ log.error e
82
+ log.error e.backtrace.join("\n")
83
+ end
84
+ end
85
+ @started = nil
86
+ end
87
+
88
+ def start_transaction
89
+ Thread::current[:custom_params] = nil
90
+ @stats_engine.start_transaction
91
+ end
92
+
93
+ def end_transaction
94
+ Thread::current[:custom_params] = nil
95
+ @stats_engine.end_transaction
96
+ end
97
+
98
+ def set_record_sql(should_record)
99
+ prev = Thread::current[:record_sql]
100
+ Thread::current[:record_sql] = should_record
101
+ prev.nil? || prev
102
+ end
103
+
104
+ def set_record_tt(should_record)
105
+ prev = Thread::current[:record_tt]
106
+ Thread::current[:record_tt] = should_record
107
+ prev.nil? || prev
108
+ end
109
+ # Push flag indicating whether we should be tracing in this
110
+ # thread.
111
+ def push_trace_execution_flag(should_trace=false)
112
+ (Thread.current[:newrelic_untraced] ||= []) << should_trace
113
+ end
114
+
115
+ # Pop the current trace execution status. Restore trace execution status
116
+ # to what it was before we pushed the current flag.
117
+ def pop_trace_execution_flag
118
+ Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
119
+ end
120
+
121
+ def add_custom_parameters(params)
122
+ p = Thread::current[:custom_params] || (Thread::current[:custom_params] = {})
123
+
124
+ p.merge!(params)
125
+ end
126
+
127
+ def custom_params
128
+ Thread::current[:custom_params] || {}
129
+ end
130
+
131
+ def set_sql_obfuscator(type, &block)
132
+ if type == :before
133
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
134
+ elsif type == :after
135
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
136
+ elsif type == :replace
137
+ @obfuscator = block
138
+ else
139
+ fail "unknown sql_obfuscator type #{type}"
140
+ end
141
+ end
142
+
143
+ def log
144
+ NewRelic::Control.instance.log
145
+ end
146
+
147
+ # Start up the agent. This verifies that the agent_enabled? is
148
+ # true and initializes the sampler based on the current
149
+ # controluration settings. Then it will fire up the background
150
+ # thread for sending data to the server if applicable.
151
+ def start
152
+ if started?
153
+ control.log! "Agent Started Already!", :error
154
+ return
155
+ end
156
+ return if !control.agent_enabled?
157
+
158
+ @local_host = determine_host
159
+
160
+ log.info "Web container: #{control.dispatcher.to_s}"
161
+
162
+ if control.dispatcher == :passenger
163
+ log.warn "Phusion Passenger has been detected. Some RPM memory statistics may have inaccuracies due to short process lifespans."
164
+ end
165
+
166
+ @started = true
167
+
168
+ sampler_config = control.fetch('transaction_tracer', {})
169
+ @use_transaction_sampler = sampler_config.fetch('enabled', true)
170
+
171
+ @record_sql = sampler_config.fetch('record_sql', :obfuscated).to_sym
172
+
173
+ # use transaction_threshold: 4.0 to force the TT collection
174
+ # threshold to 4 seconds
175
+ # use transaction_threshold: apdex_f to use your apdex t value
176
+ # multiplied by 4
177
+ # undefined transaction_threshold defaults to 2.0
178
+ apdex_f = 4 * NewRelic::Control.instance.apdex_t
179
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0)
180
+ if @slowest_transaction_threshold =~ /apdex_f/i
181
+ @slowest_transaction_threshold = apdex_f
182
+ end
183
+ @slowest_transaction_threshold = @slowest_transaction_threshold.to_f
184
+
185
+ if @use_transaction_sampler
186
+ log.info "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
187
+ else
188
+ log.info "Transaction tracing not enabled."
189
+ end
190
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
191
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
192
+ @random_sample = sampler_config.fetch('random_sample', false)
193
+ log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
194
+ # Initialize transaction sampler
195
+ @transaction_sampler.random_sampling = @random_sample
196
+
197
+ if control.monitor_mode?
198
+ if !control.license_key
199
+ @invalid_license = true
200
+ control.log! "No license key found. Please edit your newrelic.yml file and insert your license key.", :error
201
+ elsif control.license_key.length != 40
202
+ @invalid_license = true
203
+ control.log! "Invalid license key: #{control.license_key}", :error
204
+ else
205
+ launch_worker_thread
206
+ # When the VM shuts down, attempt to send a message to the
207
+ # server that this agent run is stopping, assuming it has
208
+ # successfully connected
209
+ # This shutdown handler doesn't work if Sinatra is running
210
+ # because it executes in the shutdown handler!
211
+ at_exit { shutdown } unless defined?(Sinatra::Default)
212
+ end
213
+ end
214
+ control.log! "New Relic RPM Agent #{NewRelic::VERSION::STRING} Initialized: pid = #{$$}"
215
+ control.log! "Agent Log found in #{NewRelic::Control.instance.log_file}" if NewRelic::Control.instance.log_file
216
+ end
217
+
218
+ private
219
+ def collector
220
+ @collector ||= control.server
221
+ end
222
+
223
+ # Connect to the server, and run the worker loop forever.
224
+ # Will not return.
225
+ def run_task_loop
226
+ # determine the reporting period (server based)
227
+ # note if the agent attempts to report more frequently than
228
+ # the specified report data, then it will be ignored.
229
+
230
+ control.log! "Reporting performance data every #{@report_period} seconds."
231
+ @task_loop.add_task(@report_period) do
232
+ harvest_and_send_timeslice_data
233
+ end
234
+
235
+ if @should_send_samples && @use_transaction_sampler
236
+ @task_loop.add_task(@report_period) do
237
+ harvest_and_send_slowest_sample
238
+ end
239
+ elsif !control.developer_mode?
240
+ # We still need the sampler for dev mode.
241
+ @transaction_sampler.disable
242
+ end
243
+
244
+ if @should_send_errors && @error_collector.enabled
245
+ @task_loop.add_task(@report_period) do
246
+ harvest_and_send_errors
247
+ end
248
+ end
249
+ log.debug("Running worker loop")
250
+ @task_loop.run
251
+ rescue StandardError
252
+ log.debug("Error in worker loop: #{$!}")
253
+ @connected = false
254
+ raise
255
+ end
256
+
257
+ def launch_worker_thread
258
+ if (control.dispatcher == :passenger && $0 =~ /ApplicationSpawner/)
259
+ log.debug "Process is passenger spawner - don't connect to RPM service"
260
+ return
261
+ end
262
+
263
+ @task_loop = WorkerLoop.new(log)
264
+
265
+ if control['check_bg_loading']
266
+ log.warn "Agent background loading checking turned on"
267
+ require 'new_relic/agent/patch_const_missing'
268
+ ClassLoadingWatcher.enable_warning
269
+ end
270
+ log.debug "Creating RPM worker thread."
271
+ @worker_thread = Thread.new do
272
+ begin
273
+ ClassLoadingWatcher.background_thread=Thread.current if control['check_bg_loading']
274
+ NewRelic::Agent.disable_all_tracing do
275
+ connect
276
+ run_task_loop if @connected
277
+ end
278
+ rescue NewRelic::Agent::ForceRestartException => e
279
+ log.info e.message
280
+ # disconnect and start over.
281
+ # clear the stats engine
282
+ @metric_ids = {}
283
+ @unsent_errors = []
284
+ @traces = nil
285
+ @unsent_timeslice_data = {}
286
+ @last_harvest_time = Time.now
287
+ @connected = false
288
+ # Wait a short time before trying to reconnect
289
+ sleep 30
290
+ retry
291
+ rescue IgnoreSilentlyException
292
+ control.log! "Unable to establish connection with the server. Run with log level set to debug for more information."
293
+ rescue StandardError => e
294
+ @connected = false
295
+ control.log! e, :error
296
+ control.log! e.backtrace.join("\n "), :error
297
+ end
298
+ end
299
+ @worker_thread['newrelic_label'] = 'Worker Loop'
300
+ end
301
+
302
+ def control
303
+ NewRelic::Control.instance
304
+ end
305
+
306
+ def initialize
307
+ @connected = false
308
+ @launch_time = Time.now
309
+
310
+ @metric_ids = {}
311
+ @histogram = NewRelic::Histogram.new(NewRelic::Control.instance.apdex_t / 10)
312
+ @stats_engine = NewRelic::Agent::StatsEngine.new
313
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new(self)
314
+ @error_collector = NewRelic::Agent::ErrorCollector.new(self)
315
+
316
+ @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
317
+
318
+ @invalid_license = false
319
+
320
+ @last_harvest_time = Time.now
321
+ end
322
+
323
+ # Connect to the server and validate the license. If successful,
324
+ # @connected has true when finished. If not successful, you can
325
+ # keep calling this. Return false if we could not establish a
326
+ # connection with the server and we should not retry, such as if
327
+ # there's a bad license key.
328
+ def connect
329
+ # wait a few seconds for the web server to boot, necessary in development
330
+ connect_retry_period = 5
331
+ connect_attempts = 0
332
+ @agent_id = nil
333
+ begin
334
+ sleep connect_retry_period.to_i
335
+ environment = control['send_environment_info'] != false ? control.local_env.snapshot : []
336
+ @agent_id ||= invoke_remote :start, @local_host, {
337
+ :pid => $$,
338
+ :launch_time => @launch_time.to_f,
339
+ :agent_version => NewRelic::VERSION::STRING,
340
+ :environment => environment,
341
+ :settings => control.settings,
342
+ :validate_seed => ENV['NR_VALIDATE_SEED'],
343
+ :validate_token => ENV['NR_VALIDATE_TOKEN'] }
344
+
345
+ host = invoke_remote(:get_redirect_host) rescue nil
346
+
347
+ @collector = control.server_from_host(host) if host
348
+
349
+ @report_period = invoke_remote :get_data_report_period, @agent_id
350
+
351
+ control.log! "Connected to NewRelic Service at #{@collector}"
352
+ log.debug "Agent ID = #{@agent_id}."
353
+
354
+ # Ask the server for permission to send transaction samples.
355
+ # determined by subscription license.
356
+ @should_send_samples = invoke_remote :should_collect_samples, @agent_id
357
+
358
+ if @should_send_samples
359
+ sampling_rate = invoke_remote :sampling_rate, @agent_id if @random_sample
360
+ @transaction_sampler.sampling_rate = sampling_rate
361
+ log.info "Transaction sample rate: #{@transaction_sampler.sampling_rate}" if sampling_rate
362
+ end
363
+
364
+ # Ask for permission to collect error data
365
+ @should_send_errors = invoke_remote :should_collect_errors, @agent_id
366
+
367
+ log.info "Transaction traces will be sent to the RPM service" if @use_transaction_sampler && @should_send_samples
368
+ log.info "Errors will be sent to the RPM service" if @error_collector.enabled && @should_send_errors
369
+
370
+ @connected = true
371
+
372
+ rescue LicenseException => e
373
+ control.log! e.message, :error
374
+ control.log! "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
375
+ @invalid_license = true
376
+ return false
377
+
378
+ rescue Timeout::Error, StandardError => e
379
+ log.info "Unable to establish connection with New Relic RPM Service at #{control.server}"
380
+ unless e.instance_of? IgnoreSilentlyException
381
+ log.error e.message
382
+ log.debug e.backtrace.join("\n")
383
+ end
384
+ # retry logic
385
+ connect_attempts += 1
386
+ case connect_attempts
387
+ when 1..2
388
+ connect_retry_period, period_msg = 60, "1 minute"
389
+ when 3..5 then
390
+ connect_retry_period, period_msg = 60 * 2, "2 minutes"
391
+ else
392
+ connect_retry_period, period_msg = 10*60, "10 minutes"
393
+ end
394
+ log.info "Will re-attempt in #{period_msg}"
395
+ retry
396
+ end
397
+ end
398
+
399
+ def determine_host
400
+ Socket.gethostname
401
+ end
402
+
403
+ def determine_home_directory
404
+ control.root
405
+ end
406
+
407
+ def harvest_and_send_timeslice_data
408
+
409
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.harvest_busy
410
+
411
+ now = Time.now
412
+
413
+ @unsent_timeslice_data ||= {}
414
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
415
+
416
+ begin
417
+ metric_ids = invoke_remote(:metric_data, @agent_id,
418
+ @last_harvest_time.to_f,
419
+ now.to_f,
420
+ @unsent_timeslice_data.values)
421
+
422
+ rescue Timeout::Error
423
+ # assume that the data was received. chances are that it was
424
+ metric_ids = nil
425
+ end
426
+
427
+ @metric_ids.merge! metric_ids if metric_ids
428
+
429
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
430
+
431
+ # if we successfully invoked this web service, then clear the unsent message cache.
432
+ @unsent_timeslice_data = {}
433
+ @last_harvest_time = now
434
+
435
+ # handle_messages
436
+
437
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
438
+ # then the metric data is downsampled for another timeslices
439
+ end
440
+
441
+ def harvest_and_send_slowest_sample
442
+ @traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
443
+
444
+ unless @traces.empty?
445
+ now = Time.now
446
+ log.debug "Sending (#{@traces.length}) transaction traces"
447
+ begin
448
+ # take the traces and prepare them for sending across the
449
+ # wire. This includes gathering SQL explanations, stripping
450
+ # out stack traces, and normalizing SQL. note that we
451
+ # explain only the sql statements whose segments' execution
452
+ # times exceed our threshold (to avoid unnecessary overhead
453
+ # of running explains on fast queries.)
454
+ traces = @traces.collect {|trace| trace.prepare_to_send(:explain_sql => @explain_threshold, :record_sql => @record_sql, :keep_backtraces => true, :explain_enabled => @explain_enabled)}
455
+
456
+
457
+ invoke_remote :transaction_sample_data, @agent_id, traces
458
+ rescue PostTooBigException
459
+ # we tried to send too much data, drop the first trace and
460
+ # try again
461
+ @traces.shift
462
+ retry
463
+ end
464
+
465
+ log.debug "#{now}: sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
466
+ end
467
+
468
+ # if we succeed sending this sample, then we don't need to keep
469
+ # the slowest sample around - it has been sent already and we
470
+ # can collect the next one
471
+ @traces = nil
472
+
473
+ # note - exceptions are logged in invoke_remote. If an
474
+ # exception is encountered here, then the slowest sample of is
475
+ # determined of the entire period since the last reported
476
+ # sample.
477
+ end
478
+
479
+ def harvest_and_send_errors
480
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
481
+ if @unsent_errors && @unsent_errors.length > 0
482
+ log.debug "Sending #{@unsent_errors.length} errors"
483
+ begin
484
+ invoke_remote :error_data, @agent_id, @unsent_errors
485
+ rescue PostTooBigException
486
+ @unsent_errors.shift
487
+ retry
488
+ end
489
+ # if the remote invocation fails, then we never clear
490
+ # @unsent_errors, and therefore we can re-attempt to send on
491
+ # the next heartbeat. Note the error collector maxes out at
492
+ # 20 instances to prevent leakage
493
+ @unsent_errors = []
494
+ end
495
+ end
496
+
497
+ def compress_data(object)
498
+ dump = Marshal.dump(object)
499
+
500
+ # we currently optimize for CPU here since we get roughly a 10x
501
+ # reduction in message size with this, and CPU overhead is at a
502
+ # premium. For extra-large posts, we use the higher compression
503
+ # since otherwise it actually errors out.
504
+
505
+ dump_size = dump.size
506
+
507
+ # small payloads don't need compression
508
+ return [dump, 'identity'] if dump_size < 2000
509
+
510
+ # medium payloads get fast compression, to save CPU
511
+ # big payloads get all the compression possible, to stay under
512
+ # the 2,000,000 byte post threshold
513
+ compression = dump_size < 2000000 ? Zlib::BEST_SPEED : Zlib::BEST_COMPRESSION
514
+
515
+ [Zlib::Deflate.deflate(dump, compression), 'deflate']
516
+ end
517
+
518
+ def check_post_size(post_string)
519
+ # TODO: define this as a config option on the server side
520
+ return if post_string.size < 2000000
521
+ log.warn "Tried to send too much data, retrying with less: #{post_string.size} bytes"
522
+ raise PostTooBigException
523
+ end
524
+
525
+ def send_request(opts)
526
+ request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'ACCEPT-ENCODING' => 'gzip', 'HOST' => opts[:collector].name)
527
+ request.content_type = "application/octet-stream"
528
+ request.body = opts[:data]
529
+
530
+ log.debug "connect to #{opts[:collector]}#{opts[:uri]}"
531
+
532
+ response = nil
533
+ http = control.http_connection(collector)
534
+ begin
535
+ timeout(@request_timeout) do
536
+ response = http.request(request)
537
+ end
538
+ rescue Timeout::Error
539
+ log.warn "Timed out trying to post data to RPM (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
540
+ raise
541
+ end
542
+ if response.is_a? Net::HTTPServiceUnavailable
543
+ log.debug(response.body || response.message)
544
+ raise IgnoreSilentlyException
545
+ elsif response.is_a? Net::HTTPGatewayTimeOut
546
+ log.debug("Timed out getting response: #{response.message}")
547
+ raise Timeout::Error, response.message
548
+ elsif !(response.is_a? Net::HTTPSuccess)
549
+ log.debug "Unexpected response from server: #{response.code}: #{response.message}"
550
+ raise IgnoreSilentlyException
551
+ end
552
+ response
553
+ end
554
+
555
+ def decompress_response(response)
556
+ if response['content-encoding'] != 'gzip'
557
+ log.debug "Uncompressed content returned"
558
+ return response.body
559
+ end
560
+ log.debug "Decompressing return value"
561
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
562
+ i.read
563
+ end
564
+
565
+ def check_for_exception(response)
566
+ dump = decompress_response(response)
567
+ value = Marshal.load(dump)
568
+ raise value if value.is_a? Exception
569
+ value
570
+ end
571
+
572
+ def remote_method_uri(method)
573
+ uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
574
+ uri << "?run_id=#{@agent_id}" if @agent_id
575
+ uri
576
+ end
577
+
578
+ # send a message via post
579
+ def invoke_remote(method, *args)
580
+ #determines whether to zip the data or send plain
581
+ post_data, encoding = compress_data(args)
582
+
583
+ # this checks to make sure mongrel won't choke on big uploads
584
+ check_post_size(post_data)
585
+
586
+ response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
587
+
588
+ # raises the right exception if the remote server tells it to die
589
+ return check_for_exception(response)
590
+ rescue ForceRestartException => e
591
+ log.info e.message
592
+ raise
593
+ rescue ForceDisconnectException => e
594
+ log.error "RPM forced this agent to disconnect (#{e.message})\n" \
595
+ "Restart this process to resume monitoring via rpm.newrelic.com."
596
+ # when a disconnect is requested, stop the current thread, which
597
+ # is the worker thread that gathers data and talks to the
598
+ # server.
599
+ @connected = false
600
+ Thread.exit
601
+ rescue SystemCallError, SocketError => e
602
+ # These include Errno connection errors
603
+ log.debug "Recoverable error connecting to the server: #{e}"
604
+ raise IgnoreSilentlyException
605
+ end
606
+
607
+ def graceful_disconnect
608
+ if @connected && !(control.server.name == "localhost" && control.dispatcher_instance_id == '3000')
609
+ begin
610
+ log.debug "Sending graceful shutdown message to #{control.server}"
611
+
612
+ @request_timeout = 10
613
+
614
+ log.debug "Sending RPM service agent run shutdown message"
615
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
616
+
617
+ log.debug "Graceful shutdown complete"
618
+
619
+ rescue Timeout::Error, StandardError
620
+ end
621
+ else
622
+ log.debug "Bypassing graceful shutdown - agent not connected"
623
+ end
624
+ end
625
+ end
626
+
627
+ end