dolores_rpm 3.2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +559 -0
- data/LICENSE +64 -0
- data/README.rdoc +179 -0
- data/bin/mongrel_rpm +33 -0
- data/bin/newrelic +13 -0
- data/bin/newrelic_cmd +5 -0
- data/cert/cacert.pem +118 -0
- data/cert/oldsite.pem +28 -0
- data/cert/site.pem +27 -0
- data/dolores_rpm-3.3.4.fork.gem +0 -0
- data/install.rb +9 -0
- data/lib/conditional_vendored_dependency_detection.rb +3 -0
- data/lib/conditional_vendored_metric_parser.rb +5 -0
- data/lib/new_relic/agent/agent.rb +1311 -0
- data/lib/new_relic/agent/beacon_configuration.rb +110 -0
- data/lib/new_relic/agent/browser_monitoring.rb +102 -0
- data/lib/new_relic/agent/busy_calculator.rb +99 -0
- data/lib/new_relic/agent/chained_call.rb +13 -0
- data/lib/new_relic/agent/database.rb +203 -0
- data/lib/new_relic/agent/error_collector.rb +251 -0
- data/lib/new_relic/agent/instrumentation/active_merchant.rb +27 -0
- data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +68 -0
- data/lib/new_relic/agent/instrumentation/authlogic.rb +19 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +424 -0
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +57 -0
- data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +52 -0
- data/lib/new_relic/agent/instrumentation/memcache.rb +80 -0
- data/lib/new_relic/agent/instrumentation/merb/controller.rb +41 -0
- data/lib/new_relic/agent/instrumentation/merb/errors.rb +29 -0
- data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +80 -0
- data/lib/new_relic/agent/instrumentation/metric_frame.rb +332 -0
- data/lib/new_relic/agent/instrumentation/net.rb +29 -0
- data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +36 -0
- data/lib/new_relic/agent/instrumentation/queue_time.rb +210 -0
- data/lib/new_relic/agent/instrumentation/rack.rb +98 -0
- data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +114 -0
- data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +42 -0
- data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +115 -0
- data/lib/new_relic/agent/instrumentation/rails/errors.rb +42 -0
- data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +118 -0
- data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +122 -0
- data/lib/new_relic/agent/instrumentation/rails3/errors.rb +37 -0
- data/lib/new_relic/agent/instrumentation/sinatra.rb +58 -0
- data/lib/new_relic/agent/instrumentation/sunspot.rb +29 -0
- data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +21 -0
- data/lib/new_relic/agent/instrumentation.rb +9 -0
- data/lib/new_relic/agent/method_tracer.rb +528 -0
- data/lib/new_relic/agent/sampler.rb +50 -0
- data/lib/new_relic/agent/samplers/cpu_sampler.rb +58 -0
- data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +40 -0
- data/lib/new_relic/agent/samplers/memory_sampler.rb +144 -0
- data/lib/new_relic/agent/samplers/object_sampler.rb +26 -0
- data/lib/new_relic/agent/shim_agent.rb +29 -0
- data/lib/new_relic/agent/sql_sampler.rb +267 -0
- data/lib/new_relic/agent/stats_engine/metric_stats.rb +187 -0
- data/lib/new_relic/agent/stats_engine/samplers.rb +95 -0
- data/lib/new_relic/agent/stats_engine/transactions.rb +208 -0
- data/lib/new_relic/agent/stats_engine.rb +25 -0
- data/lib/new_relic/agent/transaction_sample_builder.rb +101 -0
- data/lib/new_relic/agent/transaction_sampler.rb +397 -0
- data/lib/new_relic/agent/worker_loop.rb +89 -0
- data/lib/new_relic/agent.rb +454 -0
- data/lib/new_relic/collection_helper.rb +75 -0
- data/lib/new_relic/command.rb +85 -0
- data/lib/new_relic/commands/deployments.rb +105 -0
- data/lib/new_relic/commands/install.rb +80 -0
- data/lib/new_relic/control/class_methods.rb +53 -0
- data/lib/new_relic/control/configuration.rb +202 -0
- data/lib/new_relic/control/frameworks/external.rb +16 -0
- data/lib/new_relic/control/frameworks/merb.rb +31 -0
- data/lib/new_relic/control/frameworks/rails.rb +164 -0
- data/lib/new_relic/control/frameworks/rails3.rb +75 -0
- data/lib/new_relic/control/frameworks/ruby.rb +42 -0
- data/lib/new_relic/control/frameworks/sinatra.rb +20 -0
- data/lib/new_relic/control/frameworks.rb +10 -0
- data/lib/new_relic/control/instance_methods.rb +179 -0
- data/lib/new_relic/control/instrumentation.rb +100 -0
- data/lib/new_relic/control/logging_methods.rb +143 -0
- data/lib/new_relic/control/profiling.rb +25 -0
- data/lib/new_relic/control/server_methods.rb +114 -0
- data/lib/new_relic/control.rb +46 -0
- data/lib/new_relic/data_serialization.rb +157 -0
- data/lib/new_relic/delayed_job_injection.rb +46 -0
- data/lib/new_relic/language_support.rb +69 -0
- data/lib/new_relic/local_environment.rb +414 -0
- data/lib/new_relic/merbtasks.rb +6 -0
- data/lib/new_relic/metric_data.rb +51 -0
- data/lib/new_relic/metric_spec.rb +75 -0
- data/lib/new_relic/metrics.rb +9 -0
- data/lib/new_relic/noticed_error.rb +24 -0
- data/lib/new_relic/rack/browser_monitoring.rb +68 -0
- data/lib/new_relic/rack/developer_mode.rb +268 -0
- data/lib/new_relic/recipes.rb +73 -0
- data/lib/new_relic/stats.rb +388 -0
- data/lib/new_relic/timer_lib.rb +27 -0
- data/lib/new_relic/transaction_analysis/segment_summary.rb +49 -0
- data/lib/new_relic/transaction_analysis.rb +77 -0
- data/lib/new_relic/transaction_sample/composite_segment.rb +27 -0
- data/lib/new_relic/transaction_sample/fake_segment.rb +9 -0
- data/lib/new_relic/transaction_sample/segment.rb +201 -0
- data/lib/new_relic/transaction_sample/summary_segment.rb +21 -0
- data/lib/new_relic/transaction_sample.rb +245 -0
- data/lib/new_relic/url_rule.rb +14 -0
- data/lib/new_relic/version.rb +55 -0
- data/lib/newrelic_rpm.rb +49 -0
- data/lib/tasks/all.rb +4 -0
- data/lib/tasks/install.rake +7 -0
- data/lib/tasks/tests.rake +19 -0
- data/newrelic.yml +265 -0
- data/recipes/newrelic.rb +6 -0
- data/test/active_record_fixtures.rb +77 -0
- data/test/config/newrelic.yml +48 -0
- data/test/config/test_control.rb +48 -0
- data/test/new_relic/agent/agent/connect_test.rb +410 -0
- data/test/new_relic/agent/agent/start_test.rb +255 -0
- data/test/new_relic/agent/agent/start_worker_thread_test.rb +153 -0
- data/test/new_relic/agent/agent_test.rb +139 -0
- data/test/new_relic/agent/agent_test_controller.rb +77 -0
- data/test/new_relic/agent/agent_test_controller_test.rb +363 -0
- data/test/new_relic/agent/apdex_from_server_test.rb +9 -0
- data/test/new_relic/agent/beacon_configuration_test.rb +108 -0
- data/test/new_relic/agent/browser_monitoring_test.rb +278 -0
- data/test/new_relic/agent/busy_calculator_test.rb +81 -0
- data/test/new_relic/agent/database_test.rb +162 -0
- data/test/new_relic/agent/error_collector/notice_error_test.rb +257 -0
- data/test/new_relic/agent/error_collector_test.rb +175 -0
- data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +538 -0
- data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +36 -0
- data/test/new_relic/agent/instrumentation/instrumentation_test.rb +11 -0
- data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +172 -0
- data/test/new_relic/agent/instrumentation/metric_frame_test.rb +50 -0
- data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +84 -0
- data/test/new_relic/agent/instrumentation/queue_time_test.rb +387 -0
- data/test/new_relic/agent/instrumentation/rack_test.rb +35 -0
- data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +184 -0
- data/test/new_relic/agent/memcache_instrumentation_test.rb +143 -0
- data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +164 -0
- data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +234 -0
- data/test/new_relic/agent/method_tracer_test.rb +386 -0
- data/test/new_relic/agent/mock_scope_listener.rb +23 -0
- data/test/new_relic/agent/rpm_agent_test.rb +149 -0
- data/test/new_relic/agent/sampler_test.rb +19 -0
- data/test/new_relic/agent/shim_agent_test.rb +20 -0
- data/test/new_relic/agent/sql_sampler_test.rb +160 -0
- data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +150 -0
- data/test/new_relic/agent/stats_engine/metric_stats_test.rb +82 -0
- data/test/new_relic/agent/stats_engine/samplers_test.rb +99 -0
- data/test/new_relic/agent/stats_engine_test.rb +185 -0
- data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
- data/test/new_relic/agent/transaction_sampler_test.rb +955 -0
- data/test/new_relic/agent/worker_loop_test.rb +66 -0
- data/test/new_relic/agent_test.rb +175 -0
- data/test/new_relic/collection_helper_test.rb +149 -0
- data/test/new_relic/command/deployments_test.rb +68 -0
- data/test/new_relic/control/class_methods_test.rb +62 -0
- data/test/new_relic/control/configuration_test.rb +72 -0
- data/test/new_relic/control/logging_methods_test.rb +185 -0
- data/test/new_relic/control_test.rb +254 -0
- data/test/new_relic/data_serialization_test.rb +208 -0
- data/test/new_relic/delayed_job_injection_test.rb +16 -0
- data/test/new_relic/local_environment_test.rb +72 -0
- data/test/new_relic/metric_data_test.rb +125 -0
- data/test/new_relic/metric_spec_test.rb +95 -0
- data/test/new_relic/rack/all_test.rb +11 -0
- data/test/new_relic/rack/browser_monitoring_test.rb +84 -0
- data/test/new_relic/rack/developer_mode_helper_test.rb +141 -0
- data/test/new_relic/rack/developer_mode_test.rb +43 -0
- data/test/new_relic/stats_test.rb +426 -0
- data/test/new_relic/transaction_analysis/segment_summary_test.rb +91 -0
- data/test/new_relic/transaction_analysis_test.rb +121 -0
- data/test/new_relic/transaction_sample/composite_segment_test.rb +35 -0
- data/test/new_relic/transaction_sample/fake_segment_test.rb +17 -0
- data/test/new_relic/transaction_sample/segment_test.rb +389 -0
- data/test/new_relic/transaction_sample/summary_segment_test.rb +31 -0
- data/test/new_relic/transaction_sample_subtest_test.rb +56 -0
- data/test/new_relic/transaction_sample_test.rb +164 -0
- data/test/new_relic/version_number_test.rb +89 -0
- data/test/test_contexts.rb +29 -0
- data/test/test_helper.rb +154 -0
- data/ui/helpers/developer_mode_helper.rb +357 -0
- data/ui/helpers/google_pie_chart.rb +48 -0
- data/ui/views/layouts/newrelic_default.rhtml +47 -0
- data/ui/views/newrelic/_explain_plans.rhtml +27 -0
- data/ui/views/newrelic/_sample.rhtml +20 -0
- data/ui/views/newrelic/_segment.rhtml +28 -0
- data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
- data/ui/views/newrelic/_segment_row.rhtml +12 -0
- data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
- data/ui/views/newrelic/_show_sample_sql.rhtml +24 -0
- data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
- data/ui/views/newrelic/_sql_row.rhtml +16 -0
- data/ui/views/newrelic/_stack_trace.rhtml +15 -0
- data/ui/views/newrelic/_table.rhtml +12 -0
- data/ui/views/newrelic/explain_sql.rhtml +43 -0
- data/ui/views/newrelic/file/images/arrow-close.png +0 -0
- data/ui/views/newrelic/file/images/arrow-open.png +0 -0
- data/ui/views/newrelic/file/images/blue_bar.gif +0 -0
- data/ui/views/newrelic/file/images/file_icon.png +0 -0
- data/ui/views/newrelic/file/images/gray_bar.gif +0 -0
- data/ui/views/newrelic/file/images/new-relic-rpm-desktop.gif +0 -0
- data/ui/views/newrelic/file/images/new_relic_rpm_desktop.gif +0 -0
- data/ui/views/newrelic/file/images/textmate.png +0 -0
- data/ui/views/newrelic/file/javascript/jquery-1.4.2.js +6240 -0
- data/ui/views/newrelic/file/javascript/transaction_sample.js +120 -0
- data/ui/views/newrelic/file/stylesheets/style.css +490 -0
- data/ui/views/newrelic/index.rhtml +71 -0
- data/ui/views/newrelic/sample_not_found.rhtml +2 -0
- data/ui/views/newrelic/show_sample.rhtml +80 -0
- data/ui/views/newrelic/show_source.rhtml +3 -0
- data/ui/views/newrelic/threads.rhtml +53 -0
- data/vendor/gems/dependency_detection-0.0.1.build/LICENSE +5 -0
- data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection/version.rb +3 -0
- data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +62 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/metric_parser.rb +1 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/action_mailer.rb +14 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/active_merchant.rb +31 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/active_record.rb +33 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/apdex.rb +89 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/background_transaction.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/client.rb +46 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller.rb +67 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller_cpu.rb +43 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/controller_ext.rb +17 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/database.rb +48 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/database_pool.rb +24 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/dot_net.rb +28 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/dot_net_parser.rb +17 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/errors.rb +11 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/external.rb +55 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/frontend.rb +40 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/gc.rb +20 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/hibernate_session.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/java.rb +31 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/java_parser.rb +17 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/jsp.rb +34 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/jsp_tag.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/mem_cache.rb +55 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +122 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/orm.rb +27 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/other_transaction.rb +40 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet_context_listener.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/servlet_filter.rb +7 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr.rb +27 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr_request_handler.rb +15 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring.rb +54 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring_controller.rb +6 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/spring_view.rb +6 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/struts_action.rb +20 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/struts_result.rb +20 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/version.rb +5 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/view.rb +70 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_frontend.rb +18 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_service.rb +14 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/web_transaction.rb +133 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser.rb +64 -0
- metadata +398 -0
|
@@ -0,0 +1,1311 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'net/https'
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'zlib'
|
|
6
|
+
require 'stringio'
|
|
7
|
+
require 'new_relic/data_serialization'
|
|
8
|
+
|
|
9
|
+
module NewRelic
|
|
10
|
+
module Agent
|
|
11
|
+
|
|
12
|
+
# The Agent is a singleton that is instantiated when the plugin is
|
|
13
|
+
# activated. It collects performance data from ruby applications
|
|
14
|
+
# in realtime as the application runs, and periodically sends that
|
|
15
|
+
# data to the NewRelic server.
|
|
16
|
+
class Agent
|
|
17
|
+
|
|
18
|
+
# Specifies the version of the agent's communication protocol with
|
|
19
|
+
# the NewRelic hosted site.
|
|
20
|
+
|
|
21
|
+
PROTOCOL_VERSION = 8
|
|
22
|
+
# 14105: v8 (tag 2.10.3)
|
|
23
|
+
# (no v7)
|
|
24
|
+
# 10379: v6 (not tagged)
|
|
25
|
+
# 4078: v5 (tag 2.5.4)
|
|
26
|
+
# 2292: v4 (tag 2.3.6)
|
|
27
|
+
# 1754: v3 (tag 2.3.0)
|
|
28
|
+
# 534: v2 (shows up in 2.1.0, our first tag)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
|
|
33
|
+
@launch_time = Time.now
|
|
34
|
+
|
|
35
|
+
@metric_ids = {}
|
|
36
|
+
@stats_engine = NewRelic::Agent::StatsEngine.new
|
|
37
|
+
@transaction_sampler = NewRelic::Agent::TransactionSampler.new
|
|
38
|
+
@sql_sampler = NewRelic::Agent::SqlSampler.new
|
|
39
|
+
@stats_engine.transaction_sampler = @transaction_sampler
|
|
40
|
+
@stats_engine.sql_sampler = @sql_sampler
|
|
41
|
+
@error_collector = NewRelic::Agent::ErrorCollector.new
|
|
42
|
+
@connect_attempts = 0
|
|
43
|
+
|
|
44
|
+
@request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
|
|
45
|
+
|
|
46
|
+
@last_harvest_time = Time.now
|
|
47
|
+
@obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# contains all the class-level methods for NewRelic::Agent::Agent
|
|
51
|
+
module ClassMethods
|
|
52
|
+
# Should only be called by NewRelic::Control - returns a
|
|
53
|
+
# memoized singleton instance of the agent, creating one if needed
|
|
54
|
+
def instance
|
|
55
|
+
@instance ||= self.new
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Holds all the methods defined on NewRelic::Agent::Agent
|
|
60
|
+
# instances
|
|
61
|
+
module InstanceMethods
|
|
62
|
+
|
|
63
|
+
# holds a proc that is used to obfuscate sql statements
|
|
64
|
+
attr_reader :obfuscator
|
|
65
|
+
# the statistics engine that holds all the timeslice data
|
|
66
|
+
attr_reader :stats_engine
|
|
67
|
+
# the transaction sampler that handles recording transactions
|
|
68
|
+
attr_reader :transaction_sampler
|
|
69
|
+
attr_reader :sql_sampler
|
|
70
|
+
# error collector is a simple collection of recorded errors
|
|
71
|
+
attr_reader :error_collector
|
|
72
|
+
# whether we should record raw, obfuscated, or no sql
|
|
73
|
+
attr_reader :record_sql
|
|
74
|
+
# a cached set of metric_ids to save the collector some time -
|
|
75
|
+
# it returns a metric id for every metric name we send it, and
|
|
76
|
+
# in the future we transmit using the metric id only
|
|
77
|
+
attr_reader :metric_ids
|
|
78
|
+
# in theory a set of rules applied by the agent to the output
|
|
79
|
+
# of its metrics. Currently unimplemented
|
|
80
|
+
attr_reader :url_rules
|
|
81
|
+
# a configuration for the Real User Monitoring system -
|
|
82
|
+
# handles things like static setup of the header for inclusion
|
|
83
|
+
# into pages
|
|
84
|
+
attr_reader :beacon_configuration
|
|
85
|
+
|
|
86
|
+
# Returns the length of the unsent errors array, if it exists,
|
|
87
|
+
# otherwise nil
|
|
88
|
+
def unsent_errors_size
|
|
89
|
+
@unsent_errors.length if @unsent_errors
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the length of the traces array, if it exists,
|
|
93
|
+
# otherwise nil
|
|
94
|
+
def unsent_traces_size
|
|
95
|
+
@traces.length if @traces
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Initializes the unsent timeslice data hash, if needed, and
|
|
99
|
+
# returns the number of keys it contains
|
|
100
|
+
def unsent_timeslice_data
|
|
101
|
+
@unsent_timeslice_data ||= {}
|
|
102
|
+
@unsent_timeslice_data.keys.length
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# fakes out a transaction that did not happen in this process
|
|
106
|
+
# by creating apdex, summary metrics, and recording statistics
|
|
107
|
+
# for the transaction
|
|
108
|
+
#
|
|
109
|
+
# This method is *deprecated* - it may be removed in future
|
|
110
|
+
# versions of the agent
|
|
111
|
+
def record_transaction(duration_seconds, options={})
|
|
112
|
+
is_error = options['is_error'] || options['error_message'] || options['exception']
|
|
113
|
+
metric = options['metric']
|
|
114
|
+
metric ||= options['uri'] # normalize this with url rules
|
|
115
|
+
raise "metric or uri arguments required" unless metric
|
|
116
|
+
metric_info = NewRelic::MetricParser::MetricParser.for_metric_named(metric)
|
|
117
|
+
|
|
118
|
+
if metric_info.is_web_transaction?
|
|
119
|
+
NewRelic::Agent::Instrumentation::MetricFrame.record_apdex(metric_info, duration_seconds, duration_seconds, is_error)
|
|
120
|
+
end
|
|
121
|
+
metrics = metric_info.summary_metrics
|
|
122
|
+
|
|
123
|
+
metrics << metric
|
|
124
|
+
metrics.each do |name|
|
|
125
|
+
stats = stats_engine.get_stats_no_scope(name)
|
|
126
|
+
stats.record_data_point(duration_seconds)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if is_error
|
|
130
|
+
if options['exception']
|
|
131
|
+
e = options['exception']
|
|
132
|
+
elsif options['error_message']
|
|
133
|
+
e = Exception.new options['error_message']
|
|
134
|
+
else
|
|
135
|
+
e = Exception.new 'Unknown Error'
|
|
136
|
+
end
|
|
137
|
+
error_collector.notice_error e, :uri => options['uri'], :metric => metric
|
|
138
|
+
end
|
|
139
|
+
# busy time ?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# This method should be called in a forked process after a fork.
|
|
143
|
+
# It assumes the parent process initialized the agent, but does
|
|
144
|
+
# not assume the agent started.
|
|
145
|
+
#
|
|
146
|
+
# The call is idempotent, but not re-entrant.
|
|
147
|
+
#
|
|
148
|
+
# * It clears any metrics carried over from the parent process
|
|
149
|
+
# * Restarts the sampler thread if necessary
|
|
150
|
+
# * Initiates a new agent run and worker loop unless that was done
|
|
151
|
+
# in the parent process and +:force_reconnect+ is not true
|
|
152
|
+
#
|
|
153
|
+
# Options:
|
|
154
|
+
# * <tt>:force_reconnect => true</tt> to force the spawned process to
|
|
155
|
+
# establish a new connection, such as when forking a long running process.
|
|
156
|
+
# The default is false--it will only connect to the server if the parent
|
|
157
|
+
# had not connected.
|
|
158
|
+
# * <tt>:keep_retrying => false</tt> if we try to initiate a new
|
|
159
|
+
# connection, this tells me to only try it once so this method returns
|
|
160
|
+
# quickly if there is some kind of latency with the server.
|
|
161
|
+
def after_fork(options={})
|
|
162
|
+
|
|
163
|
+
# @connected gets false after we fail to connect or have an error
|
|
164
|
+
# connecting. @connected has nil if we haven't finished trying to connect.
|
|
165
|
+
# or we didn't attempt a connection because this is the master process
|
|
166
|
+
|
|
167
|
+
# log.debug "Agent received after_fork notice in #$$: [#{control.agent_enabled?}; monitor=#{control.monitor_mode?}; connected: #{@connected.inspect}; thread=#{@worker_thread.inspect}]"
|
|
168
|
+
return if !control.agent_enabled? or
|
|
169
|
+
!control.monitor_mode? or
|
|
170
|
+
@connected == false or
|
|
171
|
+
@worker_thread && @worker_thread.alive?
|
|
172
|
+
|
|
173
|
+
log.info "Starting the worker thread in #$$ after forking."
|
|
174
|
+
|
|
175
|
+
# Clear out stats that are left over from parent process
|
|
176
|
+
reset_stats
|
|
177
|
+
|
|
178
|
+
# Don't ever check to see if this is a spawner. If we're in a forked process
|
|
179
|
+
# I'm pretty sure we're not also forking new instances.
|
|
180
|
+
start_worker_thread(options)
|
|
181
|
+
@stats_engine.start_sampler_thread
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# True if we have initialized and completed 'start'
|
|
185
|
+
def started?
|
|
186
|
+
@started
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Return nil if not yet connected, true if successfully started
|
|
190
|
+
# and false if we failed to start.
|
|
191
|
+
def connected?
|
|
192
|
+
@connected
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Attempt a graceful shutdown of the agent, running the worker
|
|
196
|
+
# loop if it exists and is running.
|
|
197
|
+
#
|
|
198
|
+
# Options:
|
|
199
|
+
# :force_send => (true/false) # force the agent to send data
|
|
200
|
+
# before shutting down
|
|
201
|
+
def shutdown(options={})
|
|
202
|
+
run_loop_before_exit = options.fetch(:force_send, false)
|
|
203
|
+
return if not started?
|
|
204
|
+
if @worker_loop
|
|
205
|
+
@worker_loop.run_task if run_loop_before_exit
|
|
206
|
+
@worker_loop.stop
|
|
207
|
+
|
|
208
|
+
log.debug "Starting Agent shutdown"
|
|
209
|
+
|
|
210
|
+
# if litespeed, then ignore all future SIGUSR1 - it's
|
|
211
|
+
# litespeed trying to shut us down
|
|
212
|
+
|
|
213
|
+
if control.dispatcher == :litespeed
|
|
214
|
+
Signal.trap("SIGUSR1", "IGNORE")
|
|
215
|
+
Signal.trap("SIGTERM", "IGNORE")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
begin
|
|
219
|
+
NewRelic::Agent.disable_all_tracing do
|
|
220
|
+
graceful_disconnect
|
|
221
|
+
end
|
|
222
|
+
rescue => e
|
|
223
|
+
log.error e
|
|
224
|
+
log.error e.backtrace.join("\n")
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
@started = nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Tells the statistics engine we are starting a new transaction
|
|
231
|
+
def start_transaction
|
|
232
|
+
@stats_engine.start_transaction
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Tells the statistics engine we are ending a transaction
|
|
236
|
+
def end_transaction
|
|
237
|
+
@stats_engine.end_transaction
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Sets a thread local variable as to whether we should or
|
|
241
|
+
# should not record sql in the current thread. Returns the
|
|
242
|
+
# previous value, if there is one
|
|
243
|
+
def set_record_sql(should_record)
|
|
244
|
+
prev = Thread::current[:record_sql]
|
|
245
|
+
Thread::current[:record_sql] = should_record
|
|
246
|
+
prev.nil? || prev
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Sets a thread local variable as to whether we should or
|
|
250
|
+
# should not record transaction traces in the current
|
|
251
|
+
# thread. Returns the previous value, if there is one
|
|
252
|
+
def set_record_tt(should_record)
|
|
253
|
+
prev = Thread::current[:record_tt]
|
|
254
|
+
Thread::current[:record_tt] = should_record
|
|
255
|
+
prev.nil? || prev
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Push flag indicating whether we should be tracing in this
|
|
259
|
+
# thread. This uses a stack which allows us to disable tracing
|
|
260
|
+
# children of a transaction without affecting the tracing of
|
|
261
|
+
# the whole transaction
|
|
262
|
+
def push_trace_execution_flag(should_trace=false)
|
|
263
|
+
value = Thread.current[:newrelic_untraced]
|
|
264
|
+
if (value.nil?)
|
|
265
|
+
Thread.current[:newrelic_untraced] = []
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
Thread.current[:newrelic_untraced] << should_trace
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Pop the current trace execution status. Restore trace execution status
|
|
272
|
+
# to what it was before we pushed the current flag.
|
|
273
|
+
def pop_trace_execution_flag
|
|
274
|
+
Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Shorthand to the NewRelic::Agent.logger method
|
|
278
|
+
def log
|
|
279
|
+
NewRelic::Agent.logger
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Herein lies the corpse of the former 'start' method. May
|
|
283
|
+
# it's unmatched flog score rest in pieces.
|
|
284
|
+
module Start
|
|
285
|
+
# Check whether we have already started, which is an error condition
|
|
286
|
+
def already_started?
|
|
287
|
+
if started?
|
|
288
|
+
control.log!("Agent Started Already!", :error)
|
|
289
|
+
true
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# The agent is disabled when it is not force enabled by the
|
|
294
|
+
# 'agent_enabled' option (e.g. in a manual start), or
|
|
295
|
+
# enabled normally through the configuration file
|
|
296
|
+
def disabled?
|
|
297
|
+
!control.agent_enabled?
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Logs the dispatcher to the log file to assist with
|
|
301
|
+
# debugging. When no debugger is present, logs this fact to
|
|
302
|
+
# assist with proper dispatcher detection
|
|
303
|
+
def log_dispatcher
|
|
304
|
+
dispatcher_name = control.dispatcher.to_s
|
|
305
|
+
return if log_if(dispatcher_name.empty?, :info, "No dispatcher detected.")
|
|
306
|
+
log.info "Dispatcher: #{dispatcher_name}"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Logs the configured application names
|
|
310
|
+
def log_app_names
|
|
311
|
+
log.info "Application: #{control.app_names.join(", ")}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Connecting in the foreground blocks further startup of the
|
|
315
|
+
# agent until we have a connection - useful in cases where
|
|
316
|
+
# you're trying to log a very-short-running process and want
|
|
317
|
+
# to get statistics from before a server connection
|
|
318
|
+
# (typically 20 seconds) exists
|
|
319
|
+
def connect_in_foreground
|
|
320
|
+
NewRelic::Agent.disable_all_tracing { connect(:keep_retrying => false) }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# If we're using sinatra, old versions run in an at_exit
|
|
324
|
+
# block so we should probably know that
|
|
325
|
+
def using_sinatra?
|
|
326
|
+
defined?(Sinatra::Application)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# we should not set an at_exit block if people are using
|
|
330
|
+
# these as they don't do standard at_exit behavior per MRI/YARV
|
|
331
|
+
def weird_ruby?
|
|
332
|
+
NewRelic::LanguageSupport.using_engine?('rbx') ||
|
|
333
|
+
NewRelic::LanguageSupport.using_engine?('jruby') ||
|
|
334
|
+
using_sinatra?
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Installs our exit handler, which exploits the weird
|
|
338
|
+
# behavior of at_exit blocks to make sure it runs last, by
|
|
339
|
+
# doing an at_exit within an at_exit block.
|
|
340
|
+
def install_exit_handler
|
|
341
|
+
if control.send_data_on_exit && !weird_ruby?
|
|
342
|
+
# Our shutdown handler needs to run after other shutdown handlers
|
|
343
|
+
at_exit { at_exit { shutdown } }
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Tells us in the log file where the log file is
|
|
348
|
+
# located. This seems redundant, but can come in handy when
|
|
349
|
+
# we have some log file path set by the user which parses
|
|
350
|
+
# incorrectly, sending the log file to who-knows-where
|
|
351
|
+
def notify_log_file_location
|
|
352
|
+
log_file = NewRelic::Control.instance.log_file
|
|
353
|
+
log_if(File.exists?(log_file.to_s), :info,
|
|
354
|
+
"Agent Log at #{log_file}")
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Classy logging of the agent version and the current pid,
|
|
358
|
+
# so we can disambiguate processes in the log file and make
|
|
359
|
+
# sure they're running a reasonable version
|
|
360
|
+
def log_version_and_pid
|
|
361
|
+
log.info "New Relic Ruby Agent #{NewRelic::VERSION::STRING} Initialized: pid = #{$$}"
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# A helper method that logs a condition if that condition is
|
|
365
|
+
# true. Mentally cleaner than having every method set a
|
|
366
|
+
# local and log if it is true
|
|
367
|
+
def log_if(boolean, level, message)
|
|
368
|
+
self.log.send(level, message) if boolean
|
|
369
|
+
boolean
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# A helper method that logs a condition unless that
|
|
373
|
+
# condition is true. Mentally cleaner than having every
|
|
374
|
+
# method set a local and log unless it is true
|
|
375
|
+
def log_unless(boolean, level, message)
|
|
376
|
+
self.log.send(level, message) unless boolean
|
|
377
|
+
boolean
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Warn the user if they have configured their agent not to
|
|
381
|
+
# send data, that way we can see this clearly in the log file
|
|
382
|
+
def monitoring?
|
|
383
|
+
log_unless(control.monitor_mode?, :warn, "Agent configured not to send data in this environment - edit newrelic.yml to change this")
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Tell the user when the license key is missing so they can
|
|
387
|
+
# fix it by adding it to the file
|
|
388
|
+
def has_license_key?
|
|
389
|
+
log_unless(control.license_key, :error, "No license key found. Please edit your newrelic.yml file and insert your license key.")
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# A correct license key exists and is of the proper length
|
|
393
|
+
def has_correct_license_key?
|
|
394
|
+
has_license_key? && correct_license_length
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# A license key is an arbitrary 40 character string,
|
|
398
|
+
# usually looks something like a SHA1 hash
|
|
399
|
+
def correct_license_length
|
|
400
|
+
key = control.license_key
|
|
401
|
+
log_unless((key.length == 40), :error, "Invalid license key: #{key}")
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# If we're using a dispatcher that forks before serving
|
|
405
|
+
# requests, we need to wait until the children are forked
|
|
406
|
+
# before connecting, otherwise the parent process sends odd data
|
|
407
|
+
def using_forking_dispatcher?
|
|
408
|
+
log_if([:passenger, :unicorn].include?(control.dispatcher), :info, "Connecting workers after forking.")
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Sanity-check the agent configuration and start the agent,
|
|
412
|
+
# setting up the worker thread and the exit handler to shut
|
|
413
|
+
# down the agent
|
|
414
|
+
def check_config_and_start_agent
|
|
415
|
+
return unless monitoring? && has_correct_license_key?
|
|
416
|
+
return if using_forking_dispatcher?
|
|
417
|
+
connect_in_foreground if control.sync_startup
|
|
418
|
+
start_worker_thread
|
|
419
|
+
install_exit_handler
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
include Start
|
|
424
|
+
|
|
425
|
+
# Logs a bunch of data and starts the agent, if needed
|
|
426
|
+
def start
|
|
427
|
+
return if already_started? || disabled?
|
|
428
|
+
@started = true
|
|
429
|
+
@local_host = determine_host
|
|
430
|
+
log_dispatcher
|
|
431
|
+
log_app_names
|
|
432
|
+
config_transaction_tracer
|
|
433
|
+
check_config_and_start_agent
|
|
434
|
+
log_version_and_pid
|
|
435
|
+
notify_log_file_location
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Clear out the metric data, errors, and transaction traces,
|
|
439
|
+
# making sure the agent is in a fresh state
|
|
440
|
+
def reset_stats
|
|
441
|
+
@stats_engine.reset_stats
|
|
442
|
+
@unsent_errors = []
|
|
443
|
+
@traces = nil
|
|
444
|
+
@unsent_timeslice_data = {}
|
|
445
|
+
@last_harvest_time = Time.now
|
|
446
|
+
@launch_time = Time.now
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
private
|
|
450
|
+
def collector
|
|
451
|
+
@collector ||= control.server
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# All of this module used to be contained in the
|
|
455
|
+
# start_worker_thread method - this is an artifact of
|
|
456
|
+
# refactoring and can be moved, renamed, etc at will
|
|
457
|
+
module StartWorkerThread
|
|
458
|
+
|
|
459
|
+
# disable transaction sampling if disabled by the server
|
|
460
|
+
# and we're not in dev mode
|
|
461
|
+
def check_transaction_sampler_status
|
|
462
|
+
if control.developer_mode? || @should_send_samples
|
|
463
|
+
@transaction_sampler.enable
|
|
464
|
+
else
|
|
465
|
+
@transaction_sampler.disable
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def check_sql_sampler_status
|
|
470
|
+
# disable sql sampling if disabled by the server
|
|
471
|
+
# and we're not in dev mode
|
|
472
|
+
if @sql_sampler.config.fetch('enabled', true) && ['raw', 'obfuscated'].include?(@sql_sampler.config.fetch('record_sql', 'obfuscated').to_s) && @transaction_sampler.config.fetch('enabled', true)
|
|
473
|
+
@sql_sampler.enable
|
|
474
|
+
else
|
|
475
|
+
@sql_sampler.disable
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# logs info about the worker loop so users can see when the
|
|
480
|
+
# agent actually begins running in the background
|
|
481
|
+
def log_worker_loop_start
|
|
482
|
+
log.info "Reporting performance data every #{@report_period} seconds."
|
|
483
|
+
log.debug "Running worker loop"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Creates the worker loop and loads it with the instructions
|
|
487
|
+
# it should run every @report_period seconds
|
|
488
|
+
def create_and_run_worker_loop
|
|
489
|
+
@worker_loop = WorkerLoop.new
|
|
490
|
+
@worker_loop.run(@report_period) do
|
|
491
|
+
save_or_transmit_data
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Handles the case where the server tells us to restart -
|
|
496
|
+
# this clears the data, clears connection attempts, and
|
|
497
|
+
# waits a while to reconnect.
|
|
498
|
+
def handle_force_restart(error)
|
|
499
|
+
log.info error.message
|
|
500
|
+
reset_stats
|
|
501
|
+
@metric_ids = {}
|
|
502
|
+
@connected = nil
|
|
503
|
+
sleep 30
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# when a disconnect is requested, stop the current thread, which
|
|
507
|
+
# is the worker thread that gathers data and talks to the
|
|
508
|
+
# server.
|
|
509
|
+
def handle_force_disconnect(error)
|
|
510
|
+
log.error "New Relic forced this agent to disconnect (#{error.message})"
|
|
511
|
+
disconnect
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# there is a problem with connecting to the server, so we
|
|
515
|
+
# stop trying to connect and shut down the agent
|
|
516
|
+
def handle_server_connection_problem(error)
|
|
517
|
+
log.error "Unable to establish connection with the server. Run with log level set to debug for more information."
|
|
518
|
+
log.debug("#{error.class.name}: #{error.message}\n#{error.backtrace.first}")
|
|
519
|
+
disconnect
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Handles an unknown error in the worker thread by logging
|
|
523
|
+
# it and disconnecting the agent, since we are now in an
|
|
524
|
+
# unknown state
|
|
525
|
+
def handle_other_error(error)
|
|
526
|
+
log.error "Terminating worker loop: #{error.class.name}: #{error.message}\n #{error.backtrace.join("\n ")}"
|
|
527
|
+
disconnect
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# a wrapper method to handle all the errors that can happen
|
|
531
|
+
# in the connection and worker thread system. This
|
|
532
|
+
# guarantees a no-throw from the background thread.
|
|
533
|
+
def catch_errors
|
|
534
|
+
yield
|
|
535
|
+
rescue NewRelic::Agent::ForceRestartException => e
|
|
536
|
+
handle_force_restart(e)
|
|
537
|
+
retry
|
|
538
|
+
rescue NewRelic::Agent::ForceDisconnectException => e
|
|
539
|
+
handle_force_disconnect(e)
|
|
540
|
+
rescue NewRelic::Agent::ServerConnectionException => e
|
|
541
|
+
handle_server_connection_problem(e)
|
|
542
|
+
rescue Exception => e
|
|
543
|
+
handle_other_error(e)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# This is the method that is run in a new thread in order to
|
|
547
|
+
# background the harvesting and sending of data during the
|
|
548
|
+
# normal operation of the agent.
|
|
549
|
+
#
|
|
550
|
+
# Takes connection options that determine how we should
|
|
551
|
+
# connect to the server, and loops endlessly - typically we
|
|
552
|
+
# never return from this method unless we're shutting down
|
|
553
|
+
# the agent
|
|
554
|
+
def deferred_work!(connection_options)
|
|
555
|
+
catch_errors do
|
|
556
|
+
NewRelic::Agent.disable_all_tracing do
|
|
557
|
+
# We try to connect. If this returns false that means
|
|
558
|
+
# the server rejected us for a licensing reason and we should
|
|
559
|
+
# just exit the thread. If it returns nil
|
|
560
|
+
# that means it didn't try to connect because we're in the master.
|
|
561
|
+
connect(connection_options)
|
|
562
|
+
if @connected
|
|
563
|
+
check_transaction_sampler_status
|
|
564
|
+
check_sql_sampler_status
|
|
565
|
+
log_worker_loop_start
|
|
566
|
+
create_and_run_worker_loop
|
|
567
|
+
# never reaches here unless there is a problem or
|
|
568
|
+
# the agent is exiting
|
|
569
|
+
else
|
|
570
|
+
log.debug "No connection. Worker thread ending."
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
include StartWorkerThread
|
|
577
|
+
|
|
578
|
+
# Try to launch the worker thread and connect to the server.
|
|
579
|
+
#
|
|
580
|
+
# See #connect for a description of connection_options.
|
|
581
|
+
def start_worker_thread(connection_options = {})
|
|
582
|
+
log.debug "Creating Ruby Agent worker thread."
|
|
583
|
+
@worker_thread = Thread.new do
|
|
584
|
+
deferred_work!(connection_options)
|
|
585
|
+
end # thread new
|
|
586
|
+
@worker_thread['newrelic_label'] = 'Worker Loop'
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# A shorthand for NewRelic::Control.instance
|
|
590
|
+
def control
|
|
591
|
+
NewRelic::Control.instance
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# This module is an artifact of a refactoring of the connect
|
|
595
|
+
# method - all of its methods are used in that context, so it
|
|
596
|
+
# can be refactored at will. It should be fully tested
|
|
597
|
+
module Connect
|
|
598
|
+
# the frequency with which we should try to connect to the
|
|
599
|
+
# server at the moment.
|
|
600
|
+
attr_accessor :connect_retry_period
|
|
601
|
+
# number of attempts we've made to contact the server
|
|
602
|
+
attr_accessor :connect_attempts
|
|
603
|
+
|
|
604
|
+
# Disconnect just sets connected to false, which prevents
|
|
605
|
+
# the agent from trying to connect again
|
|
606
|
+
def disconnect
|
|
607
|
+
@connected = false
|
|
608
|
+
true
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# We've tried to connect if @connected is not nil, or if we
|
|
612
|
+
# are forcing reconnection (i.e. in the case of an
|
|
613
|
+
# after_fork with long running processes)
|
|
614
|
+
def tried_to_connect?(options)
|
|
615
|
+
!(@connected.nil? || options[:force_reconnect])
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# We keep trying by default, but you can disable it with the
|
|
619
|
+
# :keep_retrying option set to false
|
|
620
|
+
def should_keep_retrying?(options)
|
|
621
|
+
@keep_retrying = (options[:keep_retrying].nil? || options[:keep_retrying])
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# Retry period is a minute for each failed attempt that
|
|
625
|
+
# we've made. This should probably do some sort of sane TCP
|
|
626
|
+
# backoff to prevent hammering the server, but a minute for
|
|
627
|
+
# each attempt seems to work reasonably well.
|
|
628
|
+
def get_retry_period
|
|
629
|
+
return 600 if self.connect_attempts > 6
|
|
630
|
+
connect_attempts * 60
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def increment_retry_period! #:nodoc:
|
|
634
|
+
self.connect_retry_period=(get_retry_period)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# We should only retry when there has not been a more
|
|
638
|
+
# serious condition that would prevent it. We increment the
|
|
639
|
+
# connect attempts and the retry period, to prevent constant
|
|
640
|
+
# connection attempts, and tell the user what we're doing by
|
|
641
|
+
# logging.
|
|
642
|
+
def should_retry?
|
|
643
|
+
if @keep_retrying
|
|
644
|
+
self.connect_attempts=(connect_attempts + 1)
|
|
645
|
+
increment_retry_period!
|
|
646
|
+
log.info "Will re-attempt in #{connect_retry_period} seconds"
|
|
647
|
+
true
|
|
648
|
+
else
|
|
649
|
+
disconnect
|
|
650
|
+
false
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# When we have a problem connecting to the server, we need
|
|
655
|
+
# to tell the user what happened, since this is not an error
|
|
656
|
+
# we can handle gracefully.
|
|
657
|
+
def log_error(error)
|
|
658
|
+
log.error "Error establishing connection with New Relic Service at #{control.server}: #{error.message}"
|
|
659
|
+
log.debug error.backtrace.join("\n")
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# When the server sends us an error with the license key, we
|
|
663
|
+
# want to tell the user that something went wrong, and let
|
|
664
|
+
# them know where to go to get a valid license key
|
|
665
|
+
#
|
|
666
|
+
# After this runs, it disconnects the agent so that it will
|
|
667
|
+
# no longer try to connect to the server, saving the
|
|
668
|
+
# application and the server load
|
|
669
|
+
def handle_license_error(error)
|
|
670
|
+
log.error error.message
|
|
671
|
+
log.info "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
|
|
672
|
+
disconnect
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# If we are using a seed and token to validate the agent, we
|
|
676
|
+
# should debug log that fact so that debug logs include a
|
|
677
|
+
# clue that token authentication is what will be used
|
|
678
|
+
def log_seed_token
|
|
679
|
+
if control.validate_seed
|
|
680
|
+
log.debug "Connecting with validation seed/token: #{control.validate_seed}/#{control.validate_token}"
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Checks whether we should send environment info, and if so,
|
|
685
|
+
# returns the snapshot from the local environment
|
|
686
|
+
def environment_for_connect
|
|
687
|
+
control['send_environment_info'] != false ? control.local_env.snapshot : []
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# These validation settings are used for cases where a
|
|
691
|
+
# dynamic server is spun up for clients - partners can
|
|
692
|
+
# include a seed and token to indicate that the host is
|
|
693
|
+
# allowed to connect, rather than setting a unique hostname
|
|
694
|
+
def validate_settings
|
|
695
|
+
{
|
|
696
|
+
:seed => control.validate_seed,
|
|
697
|
+
:token => control.validate_token
|
|
698
|
+
}
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Initializes the hash of settings that we send to the
|
|
702
|
+
# server. Returns a literal hash containing the options
|
|
703
|
+
def connect_settings
|
|
704
|
+
{
|
|
705
|
+
:pid => $$,
|
|
706
|
+
:host => @local_host,
|
|
707
|
+
:app_name => control.app_names,
|
|
708
|
+
:language => 'ruby',
|
|
709
|
+
:agent_version => NewRelic::VERSION::STRING,
|
|
710
|
+
:environment => environment_for_connect,
|
|
711
|
+
:settings => control.settings,
|
|
712
|
+
:validate => validate_settings
|
|
713
|
+
}
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# Does some simple logging to make sure that our seed and
|
|
717
|
+
# token for verification are correct, then returns the
|
|
718
|
+
# connect data passed back from the server
|
|
719
|
+
def connect_to_server
|
|
720
|
+
log_seed_token
|
|
721
|
+
connect_data = invoke_remote(:connect, connect_settings)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
# Configures the error collector if the server says that we
|
|
725
|
+
# are allowed to send errors. Pretty simple, and logs at
|
|
726
|
+
# debug whether errors will or will not be sent.
|
|
727
|
+
def configure_error_collector!(server_enabled)
|
|
728
|
+
# Reinitialize the error collector
|
|
729
|
+
@error_collector = NewRelic::Agent::ErrorCollector.new
|
|
730
|
+
# Ask for permission to collect error data
|
|
731
|
+
enabled = if error_collector.config_enabled && server_enabled
|
|
732
|
+
error_collector.enabled = true
|
|
733
|
+
else
|
|
734
|
+
error_collector.enabled = false
|
|
735
|
+
end
|
|
736
|
+
log.debug "Errors will #{enabled ? '' : 'not '}be sent to the New Relic service."
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Random sampling is enabled based on a sample rate, which
|
|
740
|
+
# is the n in "every 1/n transactions is added regardless of
|
|
741
|
+
# its length".
|
|
742
|
+
#
|
|
743
|
+
# uses a sane default for sampling rate if the sampling rate
|
|
744
|
+
# is zero, since the collector currently sends '0' as a
|
|
745
|
+
# sampling rate for all accounts, which is probably for
|
|
746
|
+
# legacy reasons
|
|
747
|
+
def enable_random_samples!(sample_rate)
|
|
748
|
+
sample_rate = 10 unless sample_rate.to_i > 0
|
|
749
|
+
@transaction_sampler.random_sampling = true
|
|
750
|
+
@transaction_sampler.sampling_rate = sample_rate
|
|
751
|
+
log.info "Transaction sampling enabled, rate = #{@transaction_sampler.sampling_rate}"
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# this entire method should be done on the transaction
|
|
755
|
+
# sampler object, rather than here. We should pass in the
|
|
756
|
+
# sampler config.
|
|
757
|
+
def config_transaction_tracer
|
|
758
|
+
# Reconfigure the transaction tracer
|
|
759
|
+
@transaction_sampler.configure!
|
|
760
|
+
@sql_sampler.configure!
|
|
761
|
+
@should_send_samples = @config_should_send_samples = @transaction_sampler.config.fetch('enabled', true)
|
|
762
|
+
@should_send_random_samples = @transaction_sampler.config.fetch('random_sample', false)
|
|
763
|
+
set_sql_recording!
|
|
764
|
+
|
|
765
|
+
# default to 2.0, string 'apdex_f' will turn into your
|
|
766
|
+
# apdex * 4
|
|
767
|
+
@slowest_transaction_threshold = @transaction_sampler.config.fetch('transaction_threshold', 2.0).to_f
|
|
768
|
+
@slowest_transaction_threshold = apdex_f if apdex_f_threshold?
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
# Enables or disables the transaction tracer and sets its
|
|
772
|
+
# options based on the options provided to the
|
|
773
|
+
# method.
|
|
774
|
+
def configure_transaction_tracer!(server_enabled, sample_rate)
|
|
775
|
+
# Ask the server for permission to send transaction samples.
|
|
776
|
+
# determined by subscription license.
|
|
777
|
+
@transaction_sampler.config['enabled'] = server_enabled
|
|
778
|
+
@sql_sampler.disable unless @transaction_sampler.config['enabled']
|
|
779
|
+
|
|
780
|
+
@should_send_samples = @config_should_send_samples && server_enabled
|
|
781
|
+
|
|
782
|
+
if @should_send_samples
|
|
783
|
+
# I don't think this is ever true, but...
|
|
784
|
+
enable_random_samples!(sample_rate) if @should_send_random_samples
|
|
785
|
+
log.debug "Transaction tracing threshold is #{@slowest_transaction_threshold} seconds."
|
|
786
|
+
else
|
|
787
|
+
log.debug "Transaction traces will not be sent to the New Relic service."
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
# apdex_f is always 4 times the apdex_t
|
|
792
|
+
def apdex_f
|
|
793
|
+
(4 * NewRelic::Control.instance.apdex_t).to_f
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# If the transaction threshold is set to the string
|
|
797
|
+
# 'apdex_f', we use 4 times the apdex_t value to record
|
|
798
|
+
# transactions. This gears well with using apdex since you
|
|
799
|
+
# will attempt to send any transactions that register as 'failing'
|
|
800
|
+
def apdex_f_threshold?
|
|
801
|
+
@transaction_sampler.config.fetch('transaction_threshold', '') =~ /apdex_f/i
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Sets the sql recording configuration by trying to detect
|
|
805
|
+
# any attempt to disable the sql collection - 'off',
|
|
806
|
+
# 'false', 'none', and friends. Otherwise, we accept 'raw',
|
|
807
|
+
# and unrecognized values default to 'obfuscated'
|
|
808
|
+
def set_sql_recording!
|
|
809
|
+
record_sql_config = @transaction_sampler.config.fetch('record_sql', :obfuscated)
|
|
810
|
+
case record_sql_config.to_s
|
|
811
|
+
when 'off'
|
|
812
|
+
@record_sql = :off
|
|
813
|
+
when 'none'
|
|
814
|
+
@record_sql = :off
|
|
815
|
+
when 'false'
|
|
816
|
+
@record_sql = :off
|
|
817
|
+
when 'raw'
|
|
818
|
+
@record_sql = :raw
|
|
819
|
+
else
|
|
820
|
+
@record_sql = :obfuscated
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
log_sql_transmission_warning?
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# Warn the user when we are sending raw sql across the wire
|
|
827
|
+
# - they should probably be using ssl when this is true
|
|
828
|
+
def log_sql_transmission_warning?
|
|
829
|
+
log.warn("Agent is configured to send raw SQL to the service") if @record_sql == :raw
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# Asks the collector to tell us which sub-collector we
|
|
833
|
+
# should be reporting to, and then does the name resolution
|
|
834
|
+
# on that host so we don't block on DNS during the normal
|
|
835
|
+
# course of agent processing
|
|
836
|
+
def set_collector_host!
|
|
837
|
+
host = invoke_remote(:get_redirect_host)
|
|
838
|
+
if host
|
|
839
|
+
@collector = control.server_from_host(host)
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
# Sets the collector host and connects to the server, then
|
|
844
|
+
# invokes the final configuration with the returned data
|
|
845
|
+
def query_server_for_configuration
|
|
846
|
+
set_collector_host!
|
|
847
|
+
|
|
848
|
+
finish_setup(connect_to_server)
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# Takes a hash of configuration data returned from the
|
|
852
|
+
# server and uses it to set local variables and to
|
|
853
|
+
# initialize various parts of the agent that are configured
|
|
854
|
+
# separately.
|
|
855
|
+
#
|
|
856
|
+
# Can accommodate most arbitrary data - anything extra is
|
|
857
|
+
# ignored unless we say to do something with it here.
|
|
858
|
+
def finish_setup(config_data)
|
|
859
|
+
@agent_id = config_data['agent_run_id']
|
|
860
|
+
@report_period = config_data['data_report_period']
|
|
861
|
+
@url_rules = config_data['url_rules']
|
|
862
|
+
@beacon_configuration = BeaconConfiguration.new(config_data)
|
|
863
|
+
@server_side_config_enabled = config_data['listen_to_server_config']
|
|
864
|
+
|
|
865
|
+
control.merge_server_side_config(config_data) if @server_side_config_enabled
|
|
866
|
+
config_transaction_tracer
|
|
867
|
+
log_connection!(config_data)
|
|
868
|
+
configure_transaction_tracer!(config_data['collect_traces'], config_data['sample_rate'])
|
|
869
|
+
configure_error_collector!(config_data['collect_errors'])
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# Logs when we connect to the server, for debugging purposes
|
|
873
|
+
# - makes sure we know if an agent has not connected
|
|
874
|
+
def log_connection!(config_data)
|
|
875
|
+
control.log! "Connected to NewRelic Service at #{@collector}"
|
|
876
|
+
log.debug "Agent Run = #{@agent_id}."
|
|
877
|
+
log.debug "Connection data = #{config_data.inspect}"
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
include Connect
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# Serialize all the important data that the agent might want
|
|
884
|
+
# to send to the server. We could be sending this to file (
|
|
885
|
+
# common in short-running background transactions ) or
|
|
886
|
+
# alternately we could serialize via a pipe or socket to a
|
|
887
|
+
# local aggregation device
|
|
888
|
+
def serialize
|
|
889
|
+
accumulator = []
|
|
890
|
+
accumulator[1] = harvest_transaction_traces if @transaction_sampler
|
|
891
|
+
accumulator[2] = harvest_errors if @error_collector
|
|
892
|
+
accumulator[0] = harvest_timeslice_data
|
|
893
|
+
reset_stats
|
|
894
|
+
@metric_ids = {}
|
|
895
|
+
accumulator
|
|
896
|
+
end
|
|
897
|
+
public :serialize
|
|
898
|
+
|
|
899
|
+
# Accepts data as provided by the serialize method and merges
|
|
900
|
+
# it into our current collection of data to send. Can be
|
|
901
|
+
# dangerous if we re-merge the same data more than once - it
|
|
902
|
+
# will be sent multiple times.
|
|
903
|
+
def merge_data_from(data)
|
|
904
|
+
metrics, transaction_traces, errors = data
|
|
905
|
+
@stats_engine.merge_data(metrics) if metrics
|
|
906
|
+
if transaction_traces
|
|
907
|
+
if @traces
|
|
908
|
+
@traces = @traces + transaction_traces
|
|
909
|
+
else
|
|
910
|
+
@traces = transaction_traces
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
if errors
|
|
914
|
+
if @unsent_errors
|
|
915
|
+
@unsent_errors = @unsent_errors + errors
|
|
916
|
+
else
|
|
917
|
+
@unsent_errors = errors
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
public :merge_data_from
|
|
923
|
+
|
|
924
|
+
# Connect to the server and validate the license. If successful,
|
|
925
|
+
# @connected has true when finished. If not successful, you can
|
|
926
|
+
# keep calling this. Return false if we could not establish a
|
|
927
|
+
# connection with the server and we should not retry, such as if
|
|
928
|
+
# there's a bad license key.
|
|
929
|
+
#
|
|
930
|
+
# Set keep_retrying=false to disable retrying and return asap, such as when
|
|
931
|
+
# invoked in the foreground. Otherwise this runs until a successful
|
|
932
|
+
# connection is made, or the server rejects us.
|
|
933
|
+
#
|
|
934
|
+
# * <tt>:keep_retrying => false</tt> to only try to connect once, and
|
|
935
|
+
# return with the connection set to nil. This ensures we may try again
|
|
936
|
+
# later (default true).
|
|
937
|
+
# * <tt>force_reconnect => true</tt> if you want to establish a new connection
|
|
938
|
+
# to the server before running the worker loop. This means you get a separate
|
|
939
|
+
# agent run and New Relic sees it as a separate instance (default is false).
|
|
940
|
+
def connect(options)
|
|
941
|
+
# Don't proceed if we already connected (@connected=true) or if we tried
|
|
942
|
+
# to connect and were rejected with prejudice because of a license issue
|
|
943
|
+
# (@connected=false), unless we're forced to by force_reconnect.
|
|
944
|
+
return if tried_to_connect?(options)
|
|
945
|
+
|
|
946
|
+
# wait a few seconds for the web server to boot, necessary in development
|
|
947
|
+
@connect_retry_period = should_keep_retrying?(options) ? 10 : 0
|
|
948
|
+
|
|
949
|
+
sleep connect_retry_period
|
|
950
|
+
log.debug "Connecting Process to New Relic: #$0"
|
|
951
|
+
query_server_for_configuration
|
|
952
|
+
@connected_pid = $$
|
|
953
|
+
@connected = true
|
|
954
|
+
rescue NewRelic::Agent::LicenseException => e
|
|
955
|
+
handle_license_error(e)
|
|
956
|
+
rescue Timeout::Error, StandardError => e
|
|
957
|
+
log_error(e)
|
|
958
|
+
if should_retry?
|
|
959
|
+
retry
|
|
960
|
+
else
|
|
961
|
+
disconnect
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# Who am I? Well, this method can tell you your hostname.
|
|
966
|
+
def determine_host
|
|
967
|
+
Socket.gethostname
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
# Delegates to the control class to determine the root
|
|
971
|
+
# directory of this project
|
|
972
|
+
def determine_home_directory
|
|
973
|
+
control.root
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# Checks whether this process is a Passenger or Unicorn
|
|
977
|
+
# spawning server - if so, we probably don't intend to report
|
|
978
|
+
# statistics from this process
|
|
979
|
+
def is_application_spawner?
|
|
980
|
+
$0 =~ /ApplicationSpawner|^unicorn\S* master/
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
# calls the busy harvester and collects timeslice data to
|
|
984
|
+
# send later
|
|
985
|
+
def harvest_timeslice_data(time=Time.now)
|
|
986
|
+
# this creates timeslices that are harvested below
|
|
987
|
+
NewRelic::Agent::BusyCalculator.harvest_busy
|
|
988
|
+
|
|
989
|
+
@unsent_timeslice_data ||= {}
|
|
990
|
+
@unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
|
|
991
|
+
@unsent_timeslice_data
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
# takes an array of arrays of spec and id, adds it into the
|
|
995
|
+
# metric cache so we can save the collector some work by
|
|
996
|
+
# sending integers instead of strings
|
|
997
|
+
def fill_metric_id_cache(pairs_of_specs_and_ids)
|
|
998
|
+
Array(pairs_of_specs_and_ids).each do |metric_spec, metric_id|
|
|
999
|
+
@metric_ids[metric_spec] = metric_id
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
# note - exceptions are logged in invoke_remote. If an exception is encountered here,
|
|
1004
|
+
# then the metric data is downsampled for another
|
|
1005
|
+
# transmission later
|
|
1006
|
+
def harvest_and_send_timeslice_data
|
|
1007
|
+
now = Time.now
|
|
1008
|
+
NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote').record_data_point(0.0)
|
|
1009
|
+
NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote/metric_data').record_data_point(0.0)
|
|
1010
|
+
harvest_timeslice_data(now)
|
|
1011
|
+
begin
|
|
1012
|
+
# In this version of the protocol, we get back an assoc array of spec to id.
|
|
1013
|
+
metric_specs_and_ids = invoke_remote(:metric_data, @agent_id,
|
|
1014
|
+
@last_harvest_time.to_f,
|
|
1015
|
+
now.to_f,
|
|
1016
|
+
@unsent_timeslice_data.values)
|
|
1017
|
+
|
|
1018
|
+
rescue Timeout::Error
|
|
1019
|
+
# assume that the data was received. chances are that it
|
|
1020
|
+
# was. Also, lol.
|
|
1021
|
+
metric_specs_and_ids = []
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
fill_metric_id_cache(metric_specs_and_ids)
|
|
1025
|
+
|
|
1026
|
+
log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
|
|
1027
|
+
|
|
1028
|
+
# if we successfully invoked this web service, then clear the unsent message cache.
|
|
1029
|
+
@unsent_timeslice_data = {}
|
|
1030
|
+
@last_harvest_time = now
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
# Fills the traces array with the harvested transactions from
|
|
1034
|
+
# the transaction sampler, subject to the setting for slowest
|
|
1035
|
+
# transaction threshold
|
|
1036
|
+
def harvest_transaction_traces
|
|
1037
|
+
@traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
|
|
1038
|
+
@traces
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
def harvest_and_send_slowest_sql
|
|
1042
|
+
# FIXME add the code to try to resend if our connection is down
|
|
1043
|
+
sql_traces = @sql_sampler.harvest
|
|
1044
|
+
unless sql_traces.empty?
|
|
1045
|
+
log.debug "Sending (#{sql_traces.count}) sql traces"
|
|
1046
|
+
begin
|
|
1047
|
+
response = invoke_remote :sql_trace_data, sql_traces
|
|
1048
|
+
# log.debug "Sql trace response: #{response}"
|
|
1049
|
+
rescue
|
|
1050
|
+
@sql_sampler.merge sql_traces
|
|
1051
|
+
end
|
|
1052
|
+
end
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
# This handles getting the transaction traces and then sending
|
|
1056
|
+
# them across the wire. This includes gathering SQL
|
|
1057
|
+
# explanations, stripping out stack traces, and normalizing
|
|
1058
|
+
# SQL. note that we explain only the sql statements whose
|
|
1059
|
+
# segments' execution times exceed our threshold (to avoid
|
|
1060
|
+
# unnecessary overhead of running explains on fast queries.)
|
|
1061
|
+
def harvest_and_send_slowest_sample
|
|
1062
|
+
harvest_transaction_traces
|
|
1063
|
+
unless @traces.empty?
|
|
1064
|
+
now = Time.now
|
|
1065
|
+
log.debug "Sending (#{@traces.length}) transaction traces"
|
|
1066
|
+
begin
|
|
1067
|
+
options = { :keep_backtraces => true }
|
|
1068
|
+
options[:record_sql] = @record_sql unless @record_sql == :off
|
|
1069
|
+
if @transaction_sampler.explain_enabled
|
|
1070
|
+
options[:explain_sql] = @transaction_sampler.explain_threshold
|
|
1071
|
+
end
|
|
1072
|
+
traces = @traces.collect {|trace| trace.prepare_to_send(options)}
|
|
1073
|
+
invoke_remote :transaction_sample_data, @agent_id, traces
|
|
1074
|
+
rescue PostTooBigException
|
|
1075
|
+
# we tried to send too much data, drop the first trace and
|
|
1076
|
+
# try again
|
|
1077
|
+
retry if @traces.shift
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
log.debug "Sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
# if we succeed sending this sample, then we don't need to keep
|
|
1084
|
+
# the slowest sample around - it has been sent already and we
|
|
1085
|
+
# can clear the collection and move on
|
|
1086
|
+
@traces = nil
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
# Gets the collection of unsent errors from the error
|
|
1090
|
+
# collector. We pass back in an existing array of errors that
|
|
1091
|
+
# may be left over from a previous send
|
|
1092
|
+
def harvest_errors
|
|
1093
|
+
@unsent_errors = @error_collector.harvest_errors(@unsent_errors)
|
|
1094
|
+
@unsent_errors
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
# Handles getting the errors from the error collector and
|
|
1098
|
+
# sending them to the server, and any error cases like trying
|
|
1099
|
+
# to send very large errors - we drop the oldest error on the
|
|
1100
|
+
# floor and try again
|
|
1101
|
+
def harvest_and_send_errors
|
|
1102
|
+
harvest_errors
|
|
1103
|
+
if @unsent_errors && @unsent_errors.length > 0
|
|
1104
|
+
log.debug "Sending #{@unsent_errors.length} errors"
|
|
1105
|
+
begin
|
|
1106
|
+
invoke_remote :error_data, @agent_id, @unsent_errors
|
|
1107
|
+
rescue PostTooBigException
|
|
1108
|
+
@unsent_errors.shift
|
|
1109
|
+
retry
|
|
1110
|
+
end
|
|
1111
|
+
# if the remote invocation fails, then we never clear
|
|
1112
|
+
# @unsent_errors, and therefore we can re-attempt to send on
|
|
1113
|
+
# the next heartbeat. Note the error collector maxes out at
|
|
1114
|
+
# 20 instances to prevent leakage
|
|
1115
|
+
@unsent_errors = []
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
# This method handles the compression of the request body that
|
|
1120
|
+
# we are going to send to the server
|
|
1121
|
+
#
|
|
1122
|
+
# We currently optimize for CPU here since we get roughly a 10x
|
|
1123
|
+
# reduction in message size with this, and CPU overhead is at a
|
|
1124
|
+
# premium. For extra-large posts, we use the higher compression
|
|
1125
|
+
# since otherwise it actually errors out.
|
|
1126
|
+
#
|
|
1127
|
+
# We do not compress if content is smaller than 64kb. There are
|
|
1128
|
+
# problems with bugs in Ruby in some versions that expose us
|
|
1129
|
+
# to a risk of segfaults if we compress aggressively.
|
|
1130
|
+
#
|
|
1131
|
+
# medium payloads get fast compression, to save CPU
|
|
1132
|
+
# big payloads get all the compression possible, to stay under
|
|
1133
|
+
# the 2,000,000 byte post threshold
|
|
1134
|
+
def compress_data(object)
|
|
1135
|
+
dump = Marshal.dump(object)
|
|
1136
|
+
|
|
1137
|
+
# this checks to make sure mongrel won't choke on big uploads
|
|
1138
|
+
check_post_size(dump)
|
|
1139
|
+
|
|
1140
|
+
dump_size = dump.size
|
|
1141
|
+
|
|
1142
|
+
return [dump, 'identity'] if dump_size < (64*1024)
|
|
1143
|
+
|
|
1144
|
+
compression = dump_size < 2000000 ? Zlib::BEST_SPEED : Zlib::BEST_COMPRESSION
|
|
1145
|
+
|
|
1146
|
+
[Zlib::Deflate.deflate(dump, compression), 'deflate']
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
# Raises a PostTooBigException if the post_string is longer
|
|
1150
|
+
# than the limit configured in the control object
|
|
1151
|
+
def check_post_size(post_string)
|
|
1152
|
+
# TODO: define this as a config option on the server side
|
|
1153
|
+
return if post_string.size < control.post_size_limit
|
|
1154
|
+
log.warn "Tried to send too much data: #{post_string.size} bytes"
|
|
1155
|
+
raise PostTooBigException
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
# Posts to the specified server
|
|
1159
|
+
#
|
|
1160
|
+
# Options:
|
|
1161
|
+
# - :uri => the path to request on the server (a misnomer of
|
|
1162
|
+
# course)
|
|
1163
|
+
# - :encoding => the encoding to pass to the server
|
|
1164
|
+
# - :collector => a URI object that responds to the 'name' method
|
|
1165
|
+
# and returns the name of the collector to
|
|
1166
|
+
# contact
|
|
1167
|
+
# - :data => the data to send as the body of the request
|
|
1168
|
+
def send_request(opts)
|
|
1169
|
+
request = Net::HTTP::Post.new(opts[:uri], 'CONTENT-ENCODING' => opts[:encoding], 'HOST' => opts[:collector].name)
|
|
1170
|
+
request['user-agent'] = user_agent
|
|
1171
|
+
request.content_type = "application/octet-stream"
|
|
1172
|
+
request.body = opts[:data]
|
|
1173
|
+
|
|
1174
|
+
log.debug "Connect to #{opts[:collector]}#{opts[:uri]}"
|
|
1175
|
+
|
|
1176
|
+
response = nil
|
|
1177
|
+
http = control.http_connection(collector)
|
|
1178
|
+
http.read_timeout = nil
|
|
1179
|
+
begin
|
|
1180
|
+
NewRelic::TimerLib.timeout(@request_timeout) do
|
|
1181
|
+
response = http.request(request)
|
|
1182
|
+
end
|
|
1183
|
+
rescue Timeout::Error
|
|
1184
|
+
log.warn "Timed out trying to post data to New Relic (timeout = #{@request_timeout} seconds)" unless @request_timeout < 30
|
|
1185
|
+
raise
|
|
1186
|
+
end
|
|
1187
|
+
if response.is_a? Net::HTTPServiceUnavailable
|
|
1188
|
+
raise NewRelic::Agent::ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}"
|
|
1189
|
+
elsif response.is_a? Net::HTTPGatewayTimeOut
|
|
1190
|
+
log.debug("Timed out getting response: #{response.message}")
|
|
1191
|
+
raise Timeout::Error, response.message
|
|
1192
|
+
elsif response.is_a? Net::HTTPRequestEntityTooLarge
|
|
1193
|
+
raise PostTooBigException
|
|
1194
|
+
elsif !(response.is_a? Net::HTTPSuccess)
|
|
1195
|
+
raise NewRelic::Agent::ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}"
|
|
1196
|
+
end
|
|
1197
|
+
response
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
# Decompresses the response from the server, if it is gzip
|
|
1201
|
+
# encoded, otherwise returns it verbatim
|
|
1202
|
+
def decompress_response(response)
|
|
1203
|
+
if response['content-encoding'] != 'gzip'
|
|
1204
|
+
log.debug "Uncompressed content returned"
|
|
1205
|
+
return response.body
|
|
1206
|
+
end
|
|
1207
|
+
log.debug "Decompressing return value"
|
|
1208
|
+
i = Zlib::GzipReader.new(StringIO.new(response.body))
|
|
1209
|
+
i.read
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
# unmarshals the response and raises it if it is an exception,
|
|
1213
|
+
# so we can handle it in nonlocally
|
|
1214
|
+
def check_for_exception(response)
|
|
1215
|
+
dump = decompress_response(response)
|
|
1216
|
+
value = Marshal.load(dump)
|
|
1217
|
+
raise value if value.is_a? Exception
|
|
1218
|
+
value
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
# The path on the server that we should post our data to
|
|
1222
|
+
def remote_method_uri(method)
|
|
1223
|
+
uri = "/agent_listener/#{PROTOCOL_VERSION}/#{control.license_key}/#{method}"
|
|
1224
|
+
uri << "?run_id=#{@agent_id}" if @agent_id
|
|
1225
|
+
uri
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
# Sets the user agent for connections to the server, to
|
|
1229
|
+
# conform with the HTTP spec and allow for debugging. Includes
|
|
1230
|
+
# the ruby version and also zlib version if available since
|
|
1231
|
+
# that may cause corrupt compression if there is a problem.
|
|
1232
|
+
def user_agent
|
|
1233
|
+
ruby_description = ''
|
|
1234
|
+
# note the trailing space!
|
|
1235
|
+
ruby_description << "(ruby #{::RUBY_VERSION} #{::RUBY_PLATFORM}) " if defined?(::RUBY_VERSION) && defined?(::RUBY_PLATFORM)
|
|
1236
|
+
zlib_version = ''
|
|
1237
|
+
zlib_version << "zlib/#{Zlib.zlib_version}" if defined?(::Zlib) && Zlib.respond_to?(:zlib_version)
|
|
1238
|
+
"NewRelic-RubyAgent/#{NewRelic::VERSION::STRING} #{ruby_description}#{zlib_version}"
|
|
1239
|
+
end
|
|
1240
|
+
|
|
1241
|
+
# send a message via post to the actual server. This attempts
|
|
1242
|
+
# to automatically compress the data via zlib if it is large
|
|
1243
|
+
# enough to be worth compressing, and handles any errors the
|
|
1244
|
+
# server may return
|
|
1245
|
+
def invoke_remote(method, *args)
|
|
1246
|
+
now = Time.now
|
|
1247
|
+
#determines whether to zip the data or send plain
|
|
1248
|
+
post_data, encoding = compress_data(args)
|
|
1249
|
+
|
|
1250
|
+
response = send_request({:uri => remote_method_uri(method), :encoding => encoding, :collector => collector, :data => post_data})
|
|
1251
|
+
|
|
1252
|
+
# raises the right exception if the remote server tells it to die
|
|
1253
|
+
return check_for_exception(response)
|
|
1254
|
+
rescue NewRelic::Agent::ForceRestartException => e
|
|
1255
|
+
log.info e.message
|
|
1256
|
+
raise
|
|
1257
|
+
rescue SystemCallError, SocketError => e
|
|
1258
|
+
# These include Errno connection errors
|
|
1259
|
+
raise NewRelic::Agent::ServerConnectionException, "Recoverable error connecting to the server: #{e}"
|
|
1260
|
+
ensure
|
|
1261
|
+
NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote').record_data_point((Time.now - now).to_f)
|
|
1262
|
+
NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote/' + method.to_s).record_data_point((Time.now - now).to_f)
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
def save_or_transmit_data
|
|
1266
|
+
if NewRelic::DataSerialization.should_send_data?
|
|
1267
|
+
log.debug "Sending data to New Relic Service"
|
|
1268
|
+
NewRelic::Agent.load_data
|
|
1269
|
+
harvest_and_send_errors
|
|
1270
|
+
harvest_and_send_slowest_sample
|
|
1271
|
+
harvest_and_send_slowest_sql
|
|
1272
|
+
harvest_and_send_timeslice_data
|
|
1273
|
+
else
|
|
1274
|
+
log.debug "Serializing agent data to disk"
|
|
1275
|
+
NewRelic::Agent.save_data
|
|
1276
|
+
end
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
# This method contacts the server to send remaining data and
|
|
1280
|
+
# let the server know that the agent is shutting down - this
|
|
1281
|
+
# allows us to do things like accurately set the end of the
|
|
1282
|
+
# lifetime of the process
|
|
1283
|
+
#
|
|
1284
|
+
# If this process comes from a parent process, it will not
|
|
1285
|
+
# disconnect, so that the parent process can continue to send data
|
|
1286
|
+
def graceful_disconnect
|
|
1287
|
+
if @connected
|
|
1288
|
+
begin
|
|
1289
|
+
@request_timeout = 10
|
|
1290
|
+
save_or_transmit_data
|
|
1291
|
+
if @connected_pid == $$
|
|
1292
|
+
log.debug "Sending New Relic service agent run shutdown message"
|
|
1293
|
+
invoke_remote :shutdown, @agent_id, Time.now.to_f
|
|
1294
|
+
else
|
|
1295
|
+
log.debug "This agent connected from parent process #{@connected_pid}--not sending shutdown"
|
|
1296
|
+
end
|
|
1297
|
+
log.debug "Graceful disconnect complete"
|
|
1298
|
+
rescue Timeout::Error, StandardError
|
|
1299
|
+
end
|
|
1300
|
+
else
|
|
1301
|
+
log.debug "Bypassing graceful disconnect - agent not connected"
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
extend ClassMethods
|
|
1307
|
+
include InstanceMethods
|
|
1308
|
+
include BrowserMonitoring
|
|
1309
|
+
end
|
|
1310
|
+
end
|
|
1311
|
+
end
|