scout_apm 2.6.10 → 3.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.rubocop.yml +3 -11
  4. data/CHANGELOG.markdown +4 -362
  5. data/Gemfile +1 -14
  6. data/README.markdown +7 -52
  7. data/Rakefile +1 -0
  8. data/ext/allocations/allocations.c +1 -7
  9. data/ext/allocations/extconf.rb +0 -1
  10. data/ext/rusage/rusage.c +0 -26
  11. data/ext/stacks/extconf.rb +37 -0
  12. data/ext/stacks/scout_atomics.h +86 -0
  13. data/ext/stacks/stacks.c +811 -0
  14. data/lib/scout_apm/agent/logging.rb +69 -0
  15. data/lib/scout_apm/agent/reporting.rb +126 -0
  16. data/lib/scout_apm/agent.rb +259 -138
  17. data/lib/scout_apm/app_server_load.rb +15 -41
  18. data/lib/scout_apm/attribute_arranger.rb +3 -14
  19. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -70
  20. data/lib/scout_apm/background_job_integrations/sidekiq.rb +24 -31
  21. data/lib/scout_apm/background_worker.rb +12 -23
  22. data/lib/scout_apm/capacity.rb +57 -0
  23. data/lib/scout_apm/config.rb +37 -206
  24. data/lib/scout_apm/context.rb +4 -20
  25. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
  26. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
  27. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
  28. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +88 -0
  29. data/lib/scout_apm/environment.rb +28 -42
  30. data/lib/scout_apm/fake_store.rb +0 -12
  31. data/lib/scout_apm/framework_integrations/rails_2.rb +1 -2
  32. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +6 -17
  33. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  34. data/lib/scout_apm/histogram.rb +3 -12
  35. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  36. data/lib/scout_apm/instant/middleware.rb +54 -202
  37. data/lib/scout_apm/instant_reporting.rb +7 -7
  38. data/lib/scout_apm/instruments/.DS_Store +0 -0
  39. data/lib/scout_apm/instruments/action_controller_rails_2.rb +9 -15
  40. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +76 -124
  41. data/lib/scout_apm/instruments/active_record.rb +29 -324
  42. data/lib/scout_apm/instruments/delayed_job.rb +57 -0
  43. data/lib/scout_apm/instruments/elasticsearch.rb +6 -10
  44. data/lib/scout_apm/instruments/grape.rb +9 -12
  45. data/lib/scout_apm/instruments/http_client.rb +7 -14
  46. data/lib/scout_apm/instruments/influxdb.rb +6 -10
  47. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -15
  48. data/lib/scout_apm/instruments/middleware_summary.rb +5 -11
  49. data/lib/scout_apm/instruments/mongoid.rb +8 -39
  50. data/lib/scout_apm/instruments/moped.rb +6 -11
  51. data/lib/scout_apm/instruments/net_http.rb +9 -27
  52. data/lib/scout_apm/instruments/percentile_sampler.rb +23 -42
  53. data/lib/scout_apm/instruments/process/process_cpu.rb +6 -11
  54. data/lib/scout_apm/instruments/process/process_memory.rb +12 -17
  55. data/lib/scout_apm/instruments/rails_router.rb +6 -12
  56. data/lib/scout_apm/instruments/redis.rb +6 -10
  57. data/lib/scout_apm/instruments/sinatra.rb +4 -5
  58. data/lib/scout_apm/job_record.rb +2 -4
  59. data/lib/scout_apm/layaway.rb +34 -88
  60. data/lib/scout_apm/layaway_file.rb +3 -13
  61. data/lib/scout_apm/layer.rb +60 -25
  62. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +6 -7
  63. data/lib/scout_apm/layer_converters/converter_base.rb +14 -203
  64. data/lib/scout_apm/layer_converters/depth_first_walker.rb +10 -22
  65. data/lib/scout_apm/layer_converters/error_converter.rb +8 -8
  66. data/lib/scout_apm/layer_converters/job_converter.rb +50 -37
  67. data/lib/scout_apm/layer_converters/metric_converter.rb +19 -18
  68. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +13 -13
  69. data/lib/scout_apm/layer_converters/slow_job_converter.rb +116 -52
  70. data/lib/scout_apm/layer_converters/slow_request_converter.rb +120 -51
  71. data/lib/scout_apm/metric_meta.rb +5 -0
  72. data/lib/scout_apm/metric_set.rb +1 -9
  73. data/lib/scout_apm/metric_stats.rb +8 -7
  74. data/lib/scout_apm/middleware.rb +9 -7
  75. data/lib/scout_apm/reporter.rb +24 -71
  76. data/lib/scout_apm/request_histograms.rb +0 -12
  77. data/lib/scout_apm/request_manager.rb +7 -5
  78. data/lib/scout_apm/scored_item_set.rb +0 -7
  79. data/lib/scout_apm/serializers/app_server_load_serializer.rb +0 -4
  80. data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
  81. data/lib/scout_apm/serializers/directive_serializer.rb +0 -4
  82. data/lib/scout_apm/serializers/payload_serializer.rb +4 -11
  83. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +16 -35
  84. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -2
  85. data/lib/scout_apm/server_integrations/passenger.rb +1 -1
  86. data/lib/scout_apm/server_integrations/puma.rb +2 -5
  87. data/lib/scout_apm/slow_job_policy.rb +13 -25
  88. data/lib/scout_apm/slow_job_record.rb +4 -13
  89. data/lib/scout_apm/slow_request_policy.rb +13 -25
  90. data/lib/scout_apm/slow_transaction.rb +5 -25
  91. data/lib/scout_apm/store.rb +32 -99
  92. data/lib/scout_apm/trace_compactor.rb +312 -0
  93. data/lib/scout_apm/tracer.rb +31 -35
  94. data/lib/scout_apm/tracked_request.rb +95 -262
  95. data/lib/scout_apm/utils/active_record_metric_name.rb +13 -88
  96. data/lib/scout_apm/utils/backtrace_parser.rb +4 -7
  97. data/lib/scout_apm/utils/fake_stacks.rb +87 -0
  98. data/lib/scout_apm/utils/installed_gems.rb +3 -7
  99. data/lib/scout_apm/utils/klass_helper.rb +2 -8
  100. data/lib/scout_apm/utils/null_logger.rb +13 -0
  101. data/lib/scout_apm/utils/sql_sanitizer.rb +5 -16
  102. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -7
  103. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -6
  104. data/lib/scout_apm/utils/unique_id.rb +0 -27
  105. data/lib/scout_apm/version.rb +2 -1
  106. data/lib/scout_apm.rb +25 -84
  107. data/scout_apm.gemspec +3 -17
  108. data/test/test_helper.rb +3 -57
  109. data/test/unit/agent_test.rb +54 -1
  110. data/test/unit/background_job_integrations/sidekiq_test.rb +3 -0
  111. data/test/unit/config_test.rb +12 -25
  112. data/test/unit/context_test.rb +4 -4
  113. data/test/unit/histogram_test.rb +4 -25
  114. data/test/unit/ignored_uris_test.rb +1 -1
  115. data/test/unit/instruments/active_record_instruments_test.rb +5 -0
  116. data/test/unit/layaway_test.rb +2 -62
  117. data/test/unit/serializers/payload_serializer_test.rb +15 -43
  118. data/test/unit/slow_request_policy_test.rb +6 -15
  119. data/test/unit/sql_sanitizer_test.rb +6 -53
  120. data/test/unit/store_test.rb +4 -73
  121. data/test/unit/utils/active_record_metric_name_test.rb +5 -59
  122. data/test/unit/utils/backtrace_parser_test.rb +1 -6
  123. data/tester.rb +53 -0
  124. metadata +28 -229
  125. data/.travis.yml +0 -26
  126. data/Guardfile +0 -43
  127. data/gems/README.md +0 -28
  128. data/gems/octoshark.gemfile +0 -4
  129. data/gems/rails3.gemfile +0 -5
  130. data/gems/rails4.gemfile +0 -4
  131. data/gems/rails5.gemfile +0 -4
  132. data/gems/rails6.gemfile +0 -4
  133. data/lib/scout_apm/agent/exit_handler.rb +0 -65
  134. data/lib/scout_apm/agent/preconditions.rb +0 -81
  135. data/lib/scout_apm/agent_context.rb +0 -261
  136. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +0 -31
  137. data/lib/scout_apm/auto_instrument/layer.rb +0 -23
  138. data/lib/scout_apm/auto_instrument/parser.rb +0 -27
  139. data/lib/scout_apm/auto_instrument/rails.rb +0 -175
  140. data/lib/scout_apm/auto_instrument.rb +0 -5
  141. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +0 -55
  142. data/lib/scout_apm/background_job_integrations/que.rb +0 -134
  143. data/lib/scout_apm/background_job_integrations/resque.rb +0 -88
  144. data/lib/scout_apm/background_job_integrations/shoryuken.rb +0 -124
  145. data/lib/scout_apm/background_job_integrations/sneakers.rb +0 -87
  146. data/lib/scout_apm/background_recorder.rb +0 -48
  147. data/lib/scout_apm/db_query_metric_set.rb +0 -97
  148. data/lib/scout_apm/db_query_metric_stats.rb +0 -102
  149. data/lib/scout_apm/debug.rb +0 -37
  150. data/lib/scout_apm/detailed_trace.rb +0 -217
  151. data/lib/scout_apm/error.rb +0 -27
  152. data/lib/scout_apm/error_service/error_buffer.rb +0 -39
  153. data/lib/scout_apm/error_service/error_record.rb +0 -211
  154. data/lib/scout_apm/error_service/ignored_exceptions.rb +0 -66
  155. data/lib/scout_apm/error_service/middleware.rb +0 -32
  156. data/lib/scout_apm/error_service/notifier.rb +0 -33
  157. data/lib/scout_apm/error_service/payload.rb +0 -47
  158. data/lib/scout_apm/error_service/periodic_work.rb +0 -17
  159. data/lib/scout_apm/error_service/railtie.rb +0 -11
  160. data/lib/scout_apm/error_service/sidekiq.rb +0 -80
  161. data/lib/scout_apm/error_service.rb +0 -32
  162. data/lib/scout_apm/extensions/config.rb +0 -87
  163. data/lib/scout_apm/extensions/transaction_callback_payload.rb +0 -74
  164. data/lib/scout_apm/git_revision.rb +0 -59
  165. data/lib/scout_apm/instrument_manager.rb +0 -88
  166. data/lib/scout_apm/instruments/action_view.rb +0 -141
  167. data/lib/scout_apm/instruments/http.rb +0 -48
  168. data/lib/scout_apm/instruments/memcached.rb +0 -43
  169. data/lib/scout_apm/instruments/resque.rb +0 -39
  170. data/lib/scout_apm/instruments/samplers.rb +0 -11
  171. data/lib/scout_apm/layer_children_set.rb +0 -86
  172. data/lib/scout_apm/layer_converters/database_converter.rb +0 -70
  173. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +0 -38
  174. data/lib/scout_apm/layer_converters/histograms.rb +0 -15
  175. data/lib/scout_apm/layer_converters/trace_converter.rb +0 -184
  176. data/lib/scout_apm/limited_layer.rb +0 -126
  177. data/lib/scout_apm/logger.rb +0 -158
  178. data/lib/scout_apm/periodic_work.rb +0 -47
  179. data/lib/scout_apm/rack.rb +0 -26
  180. data/lib/scout_apm/remote/message.rb +0 -27
  181. data/lib/scout_apm/remote/recorder.rb +0 -57
  182. data/lib/scout_apm/remote/router.rb +0 -49
  183. data/lib/scout_apm/remote/server.rb +0 -60
  184. data/lib/scout_apm/reporting.rb +0 -143
  185. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +0 -15
  186. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +0 -21
  187. data/lib/scout_apm/synchronous_recorder.rb +0 -30
  188. data/lib/scout_apm/tasks/doctor.rb +0 -75
  189. data/lib/scout_apm/tasks/support.rb +0 -22
  190. data/lib/scout_apm/transaction.rb +0 -13
  191. data/lib/scout_apm/transaction_time_consumed.rb +0 -51
  192. data/lib/scout_apm/utils/gzip_helper.rb +0 -24
  193. data/lib/scout_apm/utils/marshal_logging.rb +0 -90
  194. data/lib/scout_apm/utils/numbers.rb +0 -14
  195. data/lib/scout_apm/utils/scm.rb +0 -14
  196. data/lib/tasks/doctor.rake +0 -11
  197. data/test/tmp/README.md +0 -17
  198. data/test/unit/agent_context_test.rb +0 -15
  199. data/test/unit/auto_instrument/assignments-instrumented.rb +0 -31
  200. data/test/unit/auto_instrument/assignments.rb +0 -31
  201. data/test/unit/auto_instrument/controller-ast.txt +0 -57
  202. data/test/unit/auto_instrument/controller-instrumented.rb +0 -49
  203. data/test/unit/auto_instrument/controller.rb +0 -49
  204. data/test/unit/auto_instrument/rescue_from-instrumented.rb +0 -13
  205. data/test/unit/auto_instrument/rescue_from.rb +0 -13
  206. data/test/unit/auto_instrument_test.rb +0 -54
  207. data/test/unit/db_query_metric_set_test.rb +0 -67
  208. data/test/unit/db_query_metric_stats_test.rb +0 -113
  209. data/test/unit/error_service/error_buffer_test.rb +0 -25
  210. data/test/unit/error_service/ignored_exceptions_test.rb +0 -49
  211. data/test/unit/extensions/periodic_callbacks_test.rb +0 -58
  212. data/test/unit/extensions/transaction_callbacks_test.rb +0 -58
  213. data/test/unit/fake_store_test.rb +0 -10
  214. data/test/unit/git_revision_test.rb +0 -15
  215. data/test/unit/instruments/active_record_test.rb +0 -40
  216. data/test/unit/instruments/net_http_test.rb +0 -27
  217. data/test/unit/instruments/percentile_sampler_test.rb +0 -133
  218. data/test/unit/layer_children_set_test.rb +0 -97
  219. data/test/unit/layer_converters/depth_first_walker_test.rb +0 -70
  220. data/test/unit/layer_converters/metric_converter_test.rb +0 -22
  221. data/test/unit/layer_converters/stubs.rb +0 -33
  222. data/test/unit/limited_layer_test.rb +0 -53
  223. data/test/unit/logger_test.rb +0 -69
  224. data/test/unit/remote/test_message.rb +0 -13
  225. data/test/unit/remote/test_router.rb +0 -33
  226. data/test/unit/remote/test_server.rb +0 -15
  227. data/test/unit/request_histograms_test.rb +0 -17
  228. data/test/unit/tracer_test.rb +0 -76
  229. data/test/unit/tracked_request_test.rb +0 -71
  230. data/test/unit/transaction_test.rb +0 -14
  231. data/test/unit/transaction_time_consumed_test.rb +0 -46
  232. data/test/unit/utils/numbers_test.rb +0 -15
  233. data/test/unit/utils/scm.rb +0 -17
@@ -1,127 +1,227 @@
1
1
  module ScoutApm
2
- # The entry-point for the ScoutApm Agent.
2
+ # The agent gathers performance data from a Ruby application. One Agent instance is created per-Ruby process.
3
3
  #
4
- # Only one Agent instance is created per-Ruby process, and it coordinates the lifecycle of the monitoring.
5
- # - initializes various data stores
6
- # - coordinates configuration & logging
7
- # - starts background threads, running periodically
8
- # - installs shutdown hooks
4
+ # Each Agent object creates a worker thread (unless monitoring is disabled or we're forking).
5
+ # The worker thread wakes up every +Agent#period+, merges in-memory metrics w/those saved to disk,
6
+ # saves tshe merged data to disk, and sends it to the Scout server.
9
7
  class Agent
10
8
  # see self.instance
11
9
  @@instance = nil
12
10
 
13
- attr_reader :context
14
-
11
+ # Accessors below are for associated classes
12
+ attr_accessor :store
13
+ attr_accessor :layaway
14
+ attr_accessor :config
15
+ attr_accessor :capacity
16
+ attr_accessor :logger
17
+ attr_accessor :log_file # path to the log file
15
18
  attr_accessor :options # options passed to the agent when +#start+ is called.
19
+ attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
20
+ attr_reader :slow_request_policy
21
+ attr_reader :slow_job_policy
22
+ attr_reader :process_start_time # used when creating slow transactions to report how far from startup the transaction was recorded.
23
+ attr_reader :ignored_uris
24
+
25
+ # Histogram of the cumulative requests since the start of the process
26
+ attr_reader :request_histograms
16
27
 
17
- attr_reader :instrument_manager
28
+ # Histogram of the requests, distinct by reporting period (minute)
29
+ # { StoreReportingPeriodTimestamp => RequestHistograms }
30
+ attr_reader :request_histograms_by_time
18
31
 
19
32
  # All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
20
33
  def self.instance(options = {})
21
34
  @@instance ||= self.new(options)
22
35
  end
23
36
 
24
- # First call of the agent. Does very little so that the object can be created, and exist.
37
+ # Note - this doesn't start instruments or the worker thread. This is handled via +#start+ as we don't
38
+ # want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn't
39
+ # be started (when forking).
25
40
  def initialize(options = {})
26
- @options = options
27
- @context = ScoutApm::AgentContext.new
41
+ @started = false
42
+ @process_start_time = Time.now
43
+ @options ||= options
44
+
45
+ # Start up without attempting to load a configuration file. We need to be
46
+ # able to lookup configuration options like "application_root" which would
47
+ # then in turn influence where the configuration file came from.
48
+ #
49
+ # Later in initialization, we reset @config to include the file.
50
+ @config = ScoutApm::Config.without_file
51
+
52
+ @slow_request_policy = ScoutApm::SlowRequestPolicy.new
53
+ @slow_job_policy = ScoutApm::SlowJobPolicy.new
54
+ @request_histograms = ScoutApm::RequestHistograms.new
55
+ @request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
56
+
57
+ @store = ScoutApm::Store.new
58
+ @layaway = ScoutApm::Layaway.new(config, environment)
59
+ @metric_lookup = Hash.new
60
+
61
+ @capacity = ScoutApm::Capacity.new
62
+ @installed_instruments = []
28
63
  end
29
64
 
30
- def logger
31
- context.logger
65
+ def environment
66
+ ScoutApm::Environment.instance
32
67
  end
33
68
 
34
- # Finishes setting up the instrumentation, configuration, and attempts to start the agent.
35
- def install(force=false)
36
- context.config = ScoutApm::Config.with_file(context, context.config.value("config_file"))
69
+ def apm_enabled?
70
+ config.value('monitor')
71
+ end
37
72
 
38
- logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
73
+ # If true, the agent will start regardless of safety checks. Currently just used for testing.
74
+ def force?
75
+ @options[:force]
76
+ end
39
77
 
40
- if should_load_instruments? || force
41
- instrument_manager.install!
42
- install_background_job_integrations
43
- install_app_server_integration
44
- else
45
- logger.info "Not Loading Instruments"
78
+ def preconditions_met?(options={})
79
+ if !apm_enabled?
80
+ logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment. #{force? ? 'Forcing agent to start' : 'Not starting agent'}"
81
+ return false unless force?
46
82
  end
47
83
 
48
- logger.info "Scout Agent [#{ScoutApm::VERSION}] Installed"
84
+ if !environment.application_name
85
+ logger.warn "An application name could not be determined. Specify the :name value in scout_apm.yml. #{force? ? 'Forcing agent to start' : 'Not starting agent'}."
86
+ return false unless force?
87
+ end
49
88
 
50
- context.installed!
89
+ if app_server_missing?(options) && background_job_missing?
90
+ if force?
91
+ logger.warn "Agent starting (forced)"
92
+ else
93
+ logger.warn "Deferring agent start. Standing by for first request"
94
+ end
95
+ return false unless force?
96
+ end
97
+
98
+ if started?
99
+ logger.warn "Already started agent."
100
+ return false
101
+ end
51
102
 
52
- @preconditions = ScoutApm::Agent::Preconditions.new
53
- if @preconditions.check?(context) || force
54
- start
103
+ if defined?(::ScoutRails)
104
+ logger.warn "ScoutAPM is incompatible with the old Scout Rails plugin. Please remove scout_rails from your Gemfile"
105
+ return false unless force?
55
106
  end
107
+
108
+ true
56
109
  end
57
110
 
58
- # Unconditionally starts the agent. This includes verifying instruments are
59
- # installed, and starting the background worker.
60
- #
61
- # The monitor precondition is checked explicitly, and we will *never* start with monitor = false
62
- #
63
- # This does not attempt to start twice
64
- def start(opts={})
65
- return unless context.config.value('monitor')
111
+ # This is called via +ScoutApm::Agent.instance.start+ when ScoutApm is required in a Ruby application.
112
+ # It initializes the agent and starts the worker thread (if appropiate).
113
+ def start(options = {})
114
+ @options.merge!(options)
66
115
 
67
- if context.started?
68
- start_background_worker unless background_worker_running?
69
- start_error_service_background_worker unless error_service_background_worker_running?
70
- return
116
+ @config = ScoutApm::Config.with_file(@config.value("config_file"))
117
+ layaway.config = config
118
+
119
+ init_logger
120
+ logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
121
+
122
+ @ignored_uris = ScoutApm::IgnoredUris.new(config.value('ignore'))
123
+
124
+ if environment.deploy_integration
125
+ logger.info "Starting monitoring for [#{environment.deploy_integration.name}]]."
126
+ return environment.deploy_integration.install
71
127
  end
72
128
 
73
- install unless context.installed?
129
+ load_instruments if should_load_instruments?(options)
74
130
 
75
- instrument_manager.install! if should_load_instruments?
131
+ return false unless preconditions_met?(options)
132
+ @started = true
133
+ logger.info "Starting monitoring for [#{environment.application_name}]. Framework [#{environment.framework}] App Server [#{environment.app_server}] Background Job Framework [#{environment.background_job_name}]."
76
134
 
77
- context.started!
135
+ [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
136
+ ScoutApm::Instruments::Process::ProcessMemory.new(logger),
137
+ ScoutApm::Instruments::PercentileSampler.new(logger, 95),
138
+ ].each { |s| store.add_sampler(s) }
78
139
 
79
- log_environment
140
+ app_server_load_hook
80
141
 
81
- # Save it into a variable to prevent it from ever running twice
82
- @app_server_load ||= AppServerLoad.new(context).run
142
+ if environment.background_job_integration
143
+ environment.background_job_integration.install
144
+ logger.info "Installed Background Job Integration [#{environment.background_job_name}]"
145
+ end
83
146
 
84
- start_background_worker
85
- start_error_service_background_worker
147
+ # start_background_worker? is true on non-forking servers, and directly
148
+ # starts the background worker. On forking servers, a server-specific
149
+ # hook is inserted to start the background worker after forking.
150
+ if start_background_worker?
151
+ start_background_worker
152
+ logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
153
+ else
154
+ environment.app_server_integration.install
155
+ logger.info "Scout Agent [#{ScoutApm::VERSION}] loaded in [#{environment.app_server}] master process. Monitoring will start after server forks its workers."
156
+ end
86
157
  end
87
158
 
88
- def instrument_manager
89
- @instrument_manager ||= ScoutApm::InstrumentManager.new(context)
159
+ # Sends a ping to APM right away, smoothes out onboarding
160
+ # Collects up any relevant info (framework, app server, system time, ruby version, etc)
161
+ def app_server_load_hook
162
+ AppServerLoad.new.run
90
163
  end
91
164
 
92
- def log_environment
93
- bg_names = context.environment.background_job_integrations.map{|bg| bg.name }.join(", ")
94
-
95
- logger.info(
96
- "Scout Agent [#{ScoutApm::VERSION}] starting for [#{context.environment.application_name}] " +
97
- "Framework [#{context.environment.framework}] " +
98
- "App Server [#{context.environment.app_server}] " +
99
- "Background Job Framework [#{bg_names}] " +
100
- "Hostname [#{context.environment.hostname}]"
101
- )
165
+ def exit_handler_supported?
166
+ if environment.sinatra?
167
+ logger.debug "Exit handler not supported for Sinatra"
168
+ false
169
+ elsif environment.jruby?
170
+ logger.debug "Exit handler not supported for JRuby"
171
+ false
172
+ elsif environment.rubinius?
173
+ logger.debug "Exit handler not supported for Rubinius"
174
+ false
175
+ else
176
+ true
177
+ end
102
178
  end
103
179
 
104
- # Attempts to install all background job integrations. This can come up if
105
- # an app has both Resque and Sidekiq - we want both to be installed if
106
- # possible, it's no harm to have the "wrong" one also installed while running.
107
- def install_background_job_integrations
108
- context.environment.background_job_integrations.each do |int|
109
- int.install
110
- logger.info "Installed Background Job Integration [#{int.name}]"
180
+ # at_exit, calls Agent#shutdown to wrapup metric reporting.
181
+ def install_exit_handler
182
+ logger.debug "Shutdown handler not supported" and return unless exit_handler_supported?
183
+ logger.debug "Installing Shutdown Handler"
184
+
185
+ at_exit do
186
+ logger.info "Shutting down Scout Agent"
187
+ # MRI 1.9 bug drops exit codes.
188
+ # http://bugs.ruby-lang.org/issues/5218
189
+ if environment.ruby_19?
190
+ status = $!.status if $!.is_a?(SystemExit)
191
+ shutdown
192
+ exit status if status
193
+ else
194
+ shutdown
195
+ end
111
196
  end
112
197
  end
113
198
 
114
- # This sets up the background worker thread to run at the correct time,
115
- # either immediately, or after a fork into the actual unicorn/puma/etc
116
- # worker
117
- def install_app_server_integration
118
- context.environment.app_server_integration.install
119
- logger.info "Installed Application Server Integration [#{context.environment.app_server}]."
199
+ # Called via an at_exit handler, it:
200
+ # (1) Stops the background worker
201
+ # (2) Stores metrics locally (forcing current-minute metrics to be written)
202
+ # It does not attempt to actually report metrics.
203
+ def shutdown
204
+ logger.info "Shutting down ScoutApm"
205
+
206
+ return if !started?
207
+
208
+ return if @shutdown
209
+ @shutdown = true
210
+
211
+ if @background_worker
212
+ logger.info("Stopping background worker")
213
+ @background_worker.stop
214
+ store.write_to_layaway(layaway, :force)
215
+ if @background_worker_thread.alive?
216
+ @background_worker_thread.wakeup
217
+ @background_worker_thread.join
218
+ end
219
+ end
220
+ ScoutApm::Instruments::Stacks.uninstall
120
221
  end
121
222
 
122
- # If true, the agent will start regardless of safety checks.
123
- def force?
124
- @options[:force]
223
+ def started?
224
+ @started
125
225
  end
126
226
 
127
227
  # The worker thread will automatically start UNLESS:
@@ -129,96 +229,117 @@ module ScoutApm
129
229
  # * A supported application server is detected, but it forks. In this case,
130
230
  # the agent is started in the forked process.
131
231
  def start_background_worker?
232
+ return true if environment.app_server == :thin
233
+ return true if environment.app_server == :webrick
132
234
  return true if force?
133
- return !context.environment.forking?
235
+ return !environment.forking?
134
236
  end
135
237
 
136
- # monitor is the key configuration here. If it is true, then we want the
137
- # instruments. If it is false, we mostly don't want them, unless you're
138
- # asking for devtrace (ie. not reporting to apm servers as a real app, but
139
- # only for local browsers).
140
- def should_load_instruments?
141
- return true if context.config.value('dev_trace')
142
- context.config.value('monitor')
238
+ def background_worker_running?
239
+ !! @background_worker_thread
143
240
  end
144
241
 
145
- #################################
146
- # Background Worker Lifecycle #
147
- #################################
148
-
149
242
  # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
150
243
  # processes data, either saving it to disk or reporting to Scout.
151
- # => true if thread & worker got started
152
- # => false if it wasn't started (either due to already running, or other preconditions)
153
- def start_background_worker(quiet=false)
154
- if !context.config.value('monitor')
155
- logger.debug "Not starting background worker as monitoring isn't enabled." unless quiet
156
- return false
157
- end
158
-
159
- if background_worker_running?
160
- logger.info "Not starting background worker, already started" unless quiet
244
+ def start_background_worker
245
+ if !apm_enabled?
246
+ logger.debug "Not starting background worker as monitoring isn't enabled."
161
247
  return false
162
248
  end
163
-
164
- if context.shutting_down?
165
- logger.info "Not starting background worker, already in process of shutting down" unless quiet
166
- return false
167
- end
168
-
249
+ logger.info "Not starting background worker, already started" and return if background_worker_running?
169
250
  logger.info "Initializing worker thread."
170
251
 
171
- ScoutApm::Agent::ExitHandler.new(context).install
252
+ install_exit_handler
172
253
 
173
- periodic_work = ScoutApm::PeriodicWork.new(context)
254
+ if ScoutApm::Agent.instance.config.value('profile')
255
+ # After we fork, setup the handlers here.
256
+ ScoutApm::Instruments::Stacks.install
257
+ ScoutApm::Instruments::Stacks.start
258
+ end
174
259
 
175
- @background_worker = ScoutApm::BackgroundWorker.new(context)
260
+ @background_worker = ScoutApm::BackgroundWorker.new
176
261
  @background_worker_thread = Thread.new do
177
262
  @background_worker.start {
178
- periodic_work.run
263
+ ScoutApm::Agent.instance.process_metrics
264
+ clean_old_percentiles
179
265
  }
180
266
  end
267
+ end
181
268
 
182
- return true
269
+ def clean_old_percentiles
270
+ request_histograms_by_time.
271
+ keys.
272
+ select {|timestamp| timestamp.age_in_seconds > 60 * 10 }.
273
+ each {|old_timestamp| request_histograms_by_time.delete(old_timestamp) }
183
274
  end
184
275
 
185
- def stop_background_worker
186
- if @background_worker
187
- logger.info("Stopping background worker")
188
- @background_worker.stop
189
- context.store.write_to_layaway(context.layaway, :force)
190
- if @background_worker_thread.alive?
191
- @background_worker_thread.wakeup
192
- @background_worker_thread.join
276
+ # If we want to skip the app_server_check, then we must load it.
277
+ def should_load_instruments?(options={})
278
+ return true if options[:skip_app_server_check]
279
+ return true if config.value('dev_trace')
280
+ return false if !apm_enabled?
281
+ environment.app_server_integration.found? || !background_job_missing?
282
+ end
283
+
284
+ # Loads the instrumention logic.
285
+ def load_instruments
286
+ if !background_job_missing?
287
+ case environment.background_job_name
288
+ when :delayed_job
289
+ install_instrument(ScoutApm::Instruments::DelayedJob)
290
+ end
291
+ else
292
+ case environment.framework
293
+ when :rails then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
294
+ when :rails3_or_4 then
295
+ install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
296
+ install_instrument(ScoutApm::Instruments::MiddlewareSummary)
297
+ install_instrument(ScoutApm::Instruments::RailsRouter)
298
+ # when :sinatra then install_instrument(ScoutApm::Instruments::Sinatra)
193
299
  end
194
300
  end
195
- end
196
301
 
197
- def background_worker_running?
198
- @background_worker_thread &&
199
- @background_worker_thread.alive? &&
200
- @background_worker &&
201
- @background_worker.running?
302
+ install_instrument(ScoutApm::Instruments::ActiveRecord)
303
+ install_instrument(ScoutApm::Instruments::Moped)
304
+ install_instrument(ScoutApm::Instruments::Mongoid)
305
+ install_instrument(ScoutApm::Instruments::NetHttp)
306
+ install_instrument(ScoutApm::Instruments::HttpClient)
307
+ install_instrument(ScoutApm::Instruments::Redis)
308
+ install_instrument(ScoutApm::Instruments::InfluxDB)
309
+ install_instrument(ScoutApm::Instruments::Elasticsearch)
310
+ install_instrument(ScoutApm::Instruments::Grape)
311
+ rescue
312
+ logger.warn "Exception loading instruments:"
313
+ logger.warn $!.message
314
+ logger.warn $!.backtrace
202
315
  end
203
316
 
204
- # seconds to batch error reports
205
- ERROR_SEND_FREQUENCY = 5
206
- def start_error_service_background_worker
207
- periodic_work = ScoutApm::ErrorService::PeriodicWork.new(context)
317
+ def install_instrument(instrument_klass)
318
+ # Don't attempt to install the same instrument twice
319
+ return if @installed_instruments.any? { |already_installed_instrument| instrument_klass === already_installed_instrument }
208
320
 
209
- @error_service_background_worker = ScoutApm::BackgroundWorker.new(context, ERROR_SEND_FREQUENCY)
210
- @error_service_background_worker_thread = Thread.new do
211
- @error_service_background_worker.start {
212
- periodic_work.run
213
- }
321
+ # Allow users to skip individual instruments via the config file
322
+ instrument_short_name = instrument_klass.name.split("::").last
323
+ if (config.value("disabled_instruments") || []).include?(instrument_short_name)
324
+ logger.info "Skipping Disabled Instrument: #{instrument_short_name} - To re-enable, change `disabled_instruments` key in scout_apm.yml"
325
+ return
214
326
  end
327
+
328
+ instance = instrument_klass.new
329
+ @installed_instruments << instance
330
+ instance.install
331
+ end
332
+
333
+ def deploy_integration
334
+ environment.deploy_integration
335
+ end
336
+
337
+ def app_server_missing?(options = {})
338
+ !environment.app_server_integration(true).found? && !options[:skip_app_server_check]
215
339
  end
216
340
 
217
- def error_service_background_worker_running?
218
- @error_service_background_worker_thread &&
219
- @error_service_background_worker_thread.alive? &&
220
- @error_service_background_worker &&
221
- @error_service_background_worker.running?
341
+ def background_job_missing?(options = {})
342
+ environment.background_job_integration.nil? && !options[:skip_background_job_check]
222
343
  end
223
344
  end
224
345
  end
@@ -1,11 +1,9 @@
1
1
  module ScoutApm
2
2
  class AppServerLoad
3
3
  attr_reader :logger
4
- attr_reader :context
5
4
 
6
- def initialize(context)
7
- @context = context
8
- @logger = context.logger
5
+ def initialize(logger=Agent.instance.logger)
6
+ @logger = logger
9
7
  end
10
8
 
11
9
  def run
@@ -15,7 +13,7 @@ module ScoutApm
15
13
  logger.debug("Full Application Startup Info: #{data.inspect}")
16
14
 
17
15
  payload = ScoutApm::Serializers::AppServerLoadSerializer.serialize(data)
18
- reporter = Reporter.new(context, :app_server_load)
16
+ reporter = Reporter.new(:app_server_load)
19
17
  reporter.report(payload)
20
18
 
21
19
  logger.debug("Finished sending Startup Info")
@@ -29,43 +27,19 @@ module ScoutApm
29
27
  end
30
28
 
31
29
  def data
32
- {
33
- :language => 'ruby',
34
- :language_version => RUBY_VERSION,
35
- :ruby_version => RUBY_VERSION, # Deprecated.
36
-
37
- :framework => to_s_safe(environment.framework_integration.human_name),
38
- :framework_version => to_s_safe(environment.framework_integration.version),
39
-
40
- :server_time => to_s_safe(Time.now),
41
- :environment => to_s_safe(environment.framework_integration.env),
42
- :app_server => to_s_safe(environment.app_server),
43
- :hostname => to_s_safe(environment.hostname),
44
- :database_engine => to_s_safe(environment.database_engine), # Detected
45
- :database_adapter => to_s_safe(environment.raw_database_adapter), # Raw
46
- :application_name => to_s_safe(environment.application_name),
47
- :libraries => ScoutApm::Utils::InstalledGems.new(context).run,
48
- :paas => to_s_safe(environment.platform_integration.name),
49
- :git_sha => to_s_safe(environment.git_revision.sha)
30
+ { :server_time => Time.now,
31
+ :framework => ScoutApm::Environment.instance.framework_integration.human_name,
32
+ :framework_version => ScoutApm::Environment.instance.framework_integration.version,
33
+ :environment => ScoutApm::Environment.instance.framework_integration.env,
34
+ :app_server => ScoutApm::Environment.instance.app_server,
35
+ :ruby_version => RUBY_VERSION,
36
+ :hostname => ScoutApm::Environment.instance.hostname,
37
+ :database_engine => ScoutApm::Environment.instance.database_engine, # Detected
38
+ :database_adapter => ScoutApm::Environment.instance.raw_database_adapter, # Raw
39
+ :application_name => ScoutApm::Environment.instance.application_name,
40
+ :libraries => ScoutApm::Utils::InstalledGems.new.run,
41
+ :paas => ScoutApm::Environment.instance.platform_integration.name
50
42
  }
51
- ensure
52
- # Sometimes :database_engine and :database_adapter can cause a reference to an AR connection.
53
- # Make sure we release all AR connections held by this thread.
54
- ActiveRecord::Base.clear_active_connections! if Utils::KlassHelper.defined?("ActiveRecord::Base")
55
- end
56
-
57
- # Calls `.to_s` on the object passed in.
58
- # Returns literal string 'to_s error' if the object does not respond to .to_s
59
- def to_s_safe(obj)
60
- if obj.respond_to?(:to_s)
61
- obj.to_s
62
- else
63
- 'to_s error'
64
- end
65
- end
66
-
67
- def environment
68
- context.environment
69
43
  end
70
44
  end
71
45
  end
@@ -5,6 +5,8 @@ module ScoutApm
5
5
  def self.call(subject, attributes_list)
6
6
  attributes_list.inject({}) do |attribute_hash, attribute|
7
7
  case attribute
8
+ when :traces
9
+ attribute_hash[attribute] = subject.traces.to_a
8
10
  when Array
9
11
  attribute_hash[attribute[0]] = subject.send(attribute[1])
10
12
  when :bucket
@@ -12,20 +14,7 @@ module ScoutApm
12
14
  when :name
13
15
  attribute_hash[attribute] = subject.bucket_name
14
16
  when Symbol
15
- data = subject.send(attribute)
16
-
17
- # Never try to `as_json` a time object, since it'll break if the
18
- # app has the Oj gem set to mimic_JSON, and there's never
19
- # anything interesting nested inside of a Time obj. We just want
20
- # the ISO8601 string (which happens later in the payload
21
- # serializing process)
22
- if data.is_a?(Time)
23
- attribute_hash[attribute] = data
24
- elsif data.respond_to?(:as_json)
25
- attribute_hash[attribute] = data.as_json
26
- else
27
- attribute_hash[attribute] = data
28
- end
17
+ attribute_hash[attribute] = subject.send(attribute)
29
18
  end
30
19
  attribute_hash
31
20
  end