scout_apm 1.4.6 → 1.5.0.pre
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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +9 -0
- data/lib/scout_apm/agent/reporting.rb +8 -6
- data/lib/scout_apm/agent.rb +10 -6
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +23 -11
- data/lib/scout_apm/call_set.rb +61 -0
- data/lib/scout_apm/config.rb +2 -1
- data/lib/scout_apm/environment.rb +12 -7
- data/lib/scout_apm/histogram.rb +124 -0
- data/lib/scout_apm/instruments/.DS_Store +0 -0
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -0
- data/lib/scout_apm/instruments/delayed_job.rb +1 -0
- data/lib/scout_apm/instruments/process/process_memory.rb +1 -1
- data/lib/scout_apm/instruments/sinatra.rb +1 -1
- data/lib/scout_apm/job_record.rb +76 -0
- data/lib/scout_apm/layaway.rb +4 -1
- data/lib/scout_apm/layaway_file.rb +4 -4
- data/lib/scout_apm/layer.rb +14 -4
- data/lib/scout_apm/layer_converters/converter_base.rb +30 -0
- data/lib/scout_apm/layer_converters/depth_first_walker.rb +36 -0
- data/lib/scout_apm/layer_converters/error_converter.rb +20 -0
- data/lib/scout_apm/layer_converters/job_converter.rb +84 -0
- data/lib/scout_apm/layer_converters/metric_converter.rb +45 -0
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +60 -0
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +88 -0
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +111 -0
- data/lib/scout_apm/metric_meta.rb +9 -0
- data/lib/scout_apm/metric_set.rb +44 -0
- data/lib/scout_apm/reporter.rb +12 -5
- data/lib/scout_apm/serializers/jobs_serializer_to_json.rb +28 -0
- data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +54 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +5 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +9 -4
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +29 -0
- data/lib/scout_apm/slow_item_set.rb +80 -0
- data/lib/scout_apm/slow_job_policy.rb +29 -0
- data/lib/scout_apm/slow_job_record.rb +33 -0
- data/lib/scout_apm/slow_transaction.rb +0 -22
- data/lib/scout_apm/stackprof_tree_collapser.rb +7 -8
- data/lib/scout_apm/store.rb +55 -35
- data/lib/scout_apm/tracked_request.rb +67 -10
- data/lib/scout_apm/utils/active_record_metric_name.rb +13 -0
- data/lib/scout_apm/utils/backtrace_parser.rb +31 -0
- data/lib/scout_apm/utils/fake_stack_prof.rb +1 -1
- data/lib/scout_apm/utils/sql_sanitizer.rb +6 -0
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +25 -5
- data/test/unit/histogram_test.rb +93 -0
- data/test/unit/serializers/payload_serializer_test.rb +5 -5
- data/test/unit/{slow_transaction_set_test.rb → slow_item_set_test.rb} +8 -8
- data/test/unit/slow_job_policy_test.rb +55 -0
- metadata +30 -9
- data/lib/scout_apm/layer_converter.rb +0 -222
- data/lib/scout_apm/request_queue_time.rb +0 -57
- data/lib/scout_apm/slow_transaction_set.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9392c2916f2f51cd4d8f6deb672069f15f3d8e68
|
4
|
+
data.tar.gz: dd764e893e93e818a22c487a0c986701153160eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8421818c942298451ee690625cb35670088740f2862ecea8d2fceb3b4cc1cb7f15439497e3b2fede3bb52cab4756e0e48f64d30c8fdb71bbafe7b33b577ed8a6
|
7
|
+
data.tar.gz: 3486c303a0509ea19042ffba56c9045329a8e91ea25181a7b0fc4817b79468853ef7d1b5608813923e488f373cf494546d0c2b842cf7b318b6693118173e0f94
|
data/CHANGELOG.markdown
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
+
# 1.5.0
|
2
|
+
|
3
|
+
* Background Job instrumentation for Sidekiq and Sidekiq-backed ActiveJob
|
4
|
+
* Collecting backtraces on n+1 calls
|
5
|
+
|
1
6
|
# 1.4.6
|
2
7
|
|
3
8
|
* Defend against a nil
|
4
9
|
|
10
|
+
# 1.5.0
|
11
|
+
|
12
|
+
* Background Job instrumentation for Sidekiq and Sidekiq-backed ActiveJob
|
13
|
+
|
5
14
|
# 1.4.5
|
6
15
|
|
7
16
|
* Instrument Elasticsearch
|
@@ -38,6 +38,9 @@ module ScoutApm
|
|
38
38
|
def deliver_period(reporting_period)
|
39
39
|
metrics = reporting_period.metrics_payload
|
40
40
|
slow_transactions = reporting_period.slow_transactions_payload
|
41
|
+
jobs = reporting_period.jobs
|
42
|
+
slow_jobs = reporting_period.slow_jobs_payload
|
43
|
+
|
41
44
|
metadata = {
|
42
45
|
:app_root => ScoutApm::Environment.instance.root.to_s,
|
43
46
|
:unique_id => ScoutApm::Utils::UniqueId.simple,
|
@@ -49,13 +52,12 @@ module ScoutApm
|
|
49
52
|
|
50
53
|
log_deliver(metrics, slow_transactions, metadata)
|
51
54
|
|
52
|
-
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
55
|
+
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs)
|
56
|
+
logger.debug("Payload: #{payload}")
|
57
|
+
|
58
|
+
reporter.report(payload, headers)
|
57
59
|
rescue => e
|
58
|
-
logger.warn "Error on checkin
|
60
|
+
logger.warn "Error on checkin"
|
59
61
|
logger.info e.message
|
60
62
|
logger.debug e.backtrace
|
61
63
|
end
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -18,6 +18,7 @@ module ScoutApm
|
|
18
18
|
attr_accessor :options # options passed to the agent when +#start+ is called.
|
19
19
|
attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
|
20
20
|
attr_reader :slow_request_policy
|
21
|
+
attr_reader :slow_job_policy
|
21
22
|
|
22
23
|
# All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
|
23
24
|
def self.instance(options = {})
|
@@ -31,6 +32,7 @@ module ScoutApm
|
|
31
32
|
@started = false
|
32
33
|
@options ||= options
|
33
34
|
@config = ScoutApm::Config.new(options[:config_path])
|
35
|
+
@slow_job_policy = ScoutApm::SlowJobPolicy.new
|
34
36
|
|
35
37
|
@store = ScoutApm::Store.new
|
36
38
|
@layaway = ScoutApm::Layaway.new
|
@@ -78,7 +80,7 @@ module ScoutApm
|
|
78
80
|
|
79
81
|
if defined?(::ScoutRails)
|
80
82
|
logger.warn "ScoutAPM is incompatible with the old Scout Rails plugin. Please remove scout_rails from your Gemfile"
|
81
|
-
return false
|
83
|
+
return false unless force?
|
82
84
|
end
|
83
85
|
|
84
86
|
true
|
@@ -108,15 +110,17 @@ module ScoutApm
|
|
108
110
|
|
109
111
|
app_server_load_hook
|
110
112
|
|
113
|
+
if environment.background_job_integration
|
114
|
+
environment.background_job_integration.install
|
115
|
+
logger.info "Installed Background Job Integration [#{environment.background_job_name}]"
|
116
|
+
end
|
117
|
+
|
111
118
|
# start_background_worker? is true on non-forking servers, and directly
|
112
119
|
# starts the background worker. On forking servers, a server-specific
|
113
120
|
# hook is inserted to start the background worker after forking.
|
114
121
|
if start_background_worker?
|
115
122
|
start_background_worker
|
116
123
|
logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
|
117
|
-
elsif environment.background_job_integration
|
118
|
-
environment.background_job_integration.install
|
119
|
-
logger.info "Scout Agent [#{ScoutApm::VERSION}] loaded in [#{environment.background_job_name}] master process. Monitoring will start after background job framework forks its workers."
|
120
124
|
else
|
121
125
|
environment.app_server_integration.install
|
122
126
|
logger.info "Scout Agent [#{ScoutApm::VERSION}] loaded in [#{environment.app_server}] master process. Monitoring will start after server forks its workers."
|
@@ -244,7 +248,7 @@ module ScoutApm
|
|
244
248
|
install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
|
245
249
|
install_instrument(ScoutApm::Instruments::MiddlewareSummary)
|
246
250
|
install_instrument(ScoutApm::Instruments::RailsRouter)
|
247
|
-
when :sinatra then install_instrument(ScoutApm::Instruments::Sinatra)
|
251
|
+
# when :sinatra then install_instrument(ScoutApm::Instruments::Sinatra)
|
248
252
|
end
|
249
253
|
end
|
250
254
|
|
@@ -272,7 +276,7 @@ module ScoutApm
|
|
272
276
|
|
273
277
|
# Allow users to skip individual instruments via the config file
|
274
278
|
instrument_short_name = instrument_klass.name.split("::").last
|
275
|
-
if
|
279
|
+
if config.value("disabled_instruments").include?(instrument_short_name)
|
276
280
|
logger.info "Skipping Disabled Instrument: #{instrument_short_name} - To re-enable, change `disabled_instruments` key in scout_apm.yml"
|
277
281
|
return
|
278
282
|
end
|
@@ -12,7 +12,7 @@ module ScoutApm
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def forking?
|
15
|
-
|
15
|
+
false
|
16
16
|
end
|
17
17
|
|
18
18
|
def install
|
@@ -20,30 +20,41 @@ module ScoutApm
|
|
20
20
|
SidekiqMiddleware.class_eval do
|
21
21
|
include ScoutApm::Tracer
|
22
22
|
end
|
23
|
+
|
23
24
|
::Sidekiq.configure_server do |config|
|
24
25
|
config.server_middleware do |chain|
|
25
26
|
chain.add SidekiqMiddleware
|
26
27
|
end
|
27
28
|
end
|
29
|
+
|
28
30
|
require 'sidekiq/processor' # sidekiq v4 has not loaded this file by this point
|
31
|
+
|
29
32
|
::Sidekiq::Processor.class_eval do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
old.bind(self).call(boss)
|
33
|
+
def initialize_with_scout(boss)
|
34
|
+
::ScoutApm::Agent.instance.start_background_worker unless ::ScoutApm::Agent.instance.background_worker_running?
|
35
|
+
initialize_without_scout(boss)
|
34
36
|
end
|
37
|
+
|
38
|
+
alias_method :initialize_without_scout, :initialize
|
39
|
+
alias_method :initialize, :initialize_with_scout
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
38
43
|
|
39
44
|
class SidekiqMiddleware
|
40
45
|
def call(worker, msg, queue)
|
41
|
-
|
42
|
-
job_class
|
43
|
-
|
46
|
+
job_class = msg["class"] # TODO: Validate this across different versions of Sidekiq
|
47
|
+
if job_class == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" && msg.has_key?("wrapped")
|
48
|
+
job_class = msg["wrapped"]
|
49
|
+
end
|
50
|
+
|
51
|
+
latency = (Time.now.to_f - (msg['enqueued_at'] || msg['created_at']))
|
44
52
|
|
45
|
-
ScoutApm::Agent.instance.store.track_one!("Queue", queue, 0, {:extra_metrics => {:latency => latency}})
|
46
53
|
req = ScoutApm::RequestManager.lookup
|
54
|
+
req.job!
|
55
|
+
req.annotate_request(:queue_latency => latency)
|
56
|
+
|
57
|
+
req.start_layer( ScoutApm::Layer.new("Queue", queue) )
|
47
58
|
req.start_layer( ScoutApm::Layer.new("Job", job_class) )
|
48
59
|
|
49
60
|
begin
|
@@ -51,9 +62,10 @@ module ScoutApm
|
|
51
62
|
rescue
|
52
63
|
req.error!
|
53
64
|
raise
|
54
|
-
ensure
|
55
|
-
req.stop_layer
|
56
65
|
end
|
66
|
+
ensure
|
67
|
+
req.stop_layer # Job
|
68
|
+
req.stop_layer # Queue
|
57
69
|
end
|
58
70
|
end
|
59
71
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class CallSet
|
3
|
+
|
4
|
+
N_PLUS_ONE_MAGIC_NUMBER = 5 # Fetch backtraces on this number of calls to a layer. The caller data is only collected on this call to limit overhead.
|
5
|
+
N_PLUS_ONE_TIME_THRESHOLD = 150/1000.0 # Minimum time in seconds before we start performing any work. This is to prevent doing a lot of work on already fast calls.
|
6
|
+
|
7
|
+
attr_reader :call_count
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@items = [] # An array of Layer descriptions that are associated w/a single Layer name (ex: User/find). Note this may contain nil items.
|
11
|
+
@grouped_items = Hash.new { |h, k| h[k] = [] } # items groups by their normalized name since multiple layers could have the same layer name.
|
12
|
+
@call_count = 0
|
13
|
+
@captured = false # cached for performance
|
14
|
+
@start_time = Time.now
|
15
|
+
@past_start_time = false # cached for performance
|
16
|
+
end
|
17
|
+
|
18
|
+
def update!(item = nil)
|
19
|
+
if @captured # No need to do any work if we've already captured a backtrace.
|
20
|
+
return
|
21
|
+
end
|
22
|
+
@call_count += 1
|
23
|
+
@items << item
|
24
|
+
if @grouped_items.any? # lazy grouping as normalizing items can be expensive.
|
25
|
+
@grouped_items[unique_name_for(item)] << item
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Limit our workload if time across this set of calls is small.
|
30
|
+
def past_time_threshold?
|
31
|
+
return true if @past_time_threshold # no need to check again once past
|
32
|
+
@past_time_threshold = (Time.now-@start_time) >= N_PLUS_ONE_TIME_THRESHOLD
|
33
|
+
end
|
34
|
+
|
35
|
+
# We're selective on capturing a backtrace for two reasons:
|
36
|
+
# * Grouping ActiveRecord calls requires us to sanitize the SQL. This isn't cheap.
|
37
|
+
# * Capturing backtraces isn't cheap.
|
38
|
+
def capture_backtrace?
|
39
|
+
if !@captured && @call_count >= N_PLUS_ONE_MAGIC_NUMBER && past_time_threshold? && at_magic_number?
|
40
|
+
@captured = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def at_magic_number?
|
45
|
+
grouped_items[unique_name_for(@items.last)].size >= N_PLUS_ONE_MAGIC_NUMBER
|
46
|
+
end
|
47
|
+
|
48
|
+
def grouped_items
|
49
|
+
if @grouped_items.any?
|
50
|
+
@grouped_items
|
51
|
+
else
|
52
|
+
@grouped_items.merge!(@items.group_by { |item| unique_name_for(item) })
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Determine this items' "hash key"
|
57
|
+
def unique_name_for(item)
|
58
|
+
item.to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/scout_apm/config.rb
CHANGED
@@ -27,7 +27,8 @@ module ScoutApm
|
|
27
27
|
'stackprof_interval' => 20000, # microseconds, 1000 = 1 millisecond, so 20k == 20 milliseconds
|
28
28
|
'uri_reporting' => 'full_path',
|
29
29
|
'report_format' => 'json',
|
30
|
-
'disabled_instruments' => []
|
30
|
+
'disabled_instruments' => [],
|
31
|
+
'enable_background_jobs' => true,
|
31
32
|
}.freeze
|
32
33
|
|
33
34
|
def initialize(config_path = nil)
|
@@ -25,7 +25,7 @@ module ScoutApm
|
|
25
25
|
|
26
26
|
BACKGROUND_JOB_INTEGRATIONS = [
|
27
27
|
ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
|
28
|
-
ScoutApm::BackgroundJobIntegrations::DelayedJob.new
|
28
|
+
# ScoutApm::BackgroundJobIntegrations::DelayedJob.new
|
29
29
|
]
|
30
30
|
|
31
31
|
FRAMEWORK_INTEGRATIONS = [
|
@@ -130,9 +130,11 @@ module ScoutApm
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def background_job_integration
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
if Agent.instance.config.value("enable_background_jobs", !Agent.instance.config.config_file_exists?)
|
134
|
+
@background_job_integration ||= BACKGROUND_JOB_INTEGRATIONS.detect {|integration| integration.present?}
|
135
|
+
else
|
136
|
+
nil
|
137
|
+
end
|
136
138
|
end
|
137
139
|
|
138
140
|
def background_job_name
|
@@ -158,11 +160,15 @@ module ScoutApm
|
|
158
160
|
end
|
159
161
|
|
160
162
|
def ruby_19?
|
161
|
-
defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
|
163
|
+
@ruby_19 ||= defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
|
162
164
|
end
|
163
165
|
|
164
166
|
def ruby_187?
|
165
|
-
defined?(RUBY_VERSION) && RUBY_VERSION.match(/^1\.8\.7/)
|
167
|
+
@ruby_187 ||= defined?(RUBY_VERSION) && RUBY_VERSION.match(/^1\.8\.7/)
|
168
|
+
end
|
169
|
+
|
170
|
+
def ruby_2?
|
171
|
+
@ruby_2 ||= defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
|
166
172
|
end
|
167
173
|
|
168
174
|
### framework checks
|
@@ -170,6 +176,5 @@ module ScoutApm
|
|
170
176
|
def sinatra?
|
171
177
|
framework_integration.name == :sinatra
|
172
178
|
end
|
173
|
-
|
174
179
|
end # class Environemnt
|
175
180
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
HistogramBin = Struct.new(:value, :count)
|
3
|
+
|
4
|
+
class NumericHistogram
|
5
|
+
attr_reader :max_bins
|
6
|
+
attr_reader :bins
|
7
|
+
attr_accessor :total
|
8
|
+
|
9
|
+
def initialize(max_bins)
|
10
|
+
@max_bins = max_bins
|
11
|
+
@bins = []
|
12
|
+
@total = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(new_value)
|
16
|
+
@total += 1
|
17
|
+
create_new_bin(new_value.to_f)
|
18
|
+
trim
|
19
|
+
end
|
20
|
+
|
21
|
+
def quantile(q)
|
22
|
+
return 0 if total == 0
|
23
|
+
|
24
|
+
if q > 1
|
25
|
+
q = q / 100.0
|
26
|
+
end
|
27
|
+
|
28
|
+
count = q.to_f * total.to_f
|
29
|
+
|
30
|
+
bins.each_with_index do |bin, index|
|
31
|
+
count -= bin.count
|
32
|
+
|
33
|
+
if count <= 0
|
34
|
+
return bin.value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# If we fell through, we were asking for the last (max) value
|
39
|
+
return bins[-1].value
|
40
|
+
end
|
41
|
+
|
42
|
+
def mean
|
43
|
+
if total == 0
|
44
|
+
return 0
|
45
|
+
end
|
46
|
+
|
47
|
+
sum = bins.inject(0) { |s, bin| s + (bin.value * bin.count) }
|
48
|
+
return sum.to_f / total.to_f
|
49
|
+
end
|
50
|
+
|
51
|
+
def combine!(other)
|
52
|
+
@bins = (other.bins + @bins).sort_by {|b| b.value }
|
53
|
+
@total += other.total
|
54
|
+
trim
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def as_json
|
59
|
+
bins.map{|b| [b.value, b.count]}
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# If we exactly match an existing bin, add to it, otherwise create a new bin holding a count for the new value.
|
65
|
+
def create_new_bin(new_value)
|
66
|
+
bins.each_with_index do |bin, index|
|
67
|
+
# If it matches exactly, increment the bin's count
|
68
|
+
if bin.value == new_value
|
69
|
+
bin.count += 1
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
# We've gone one bin too far, so insert before the current bin.
|
74
|
+
if bin.value > new_value
|
75
|
+
# Insert at this index
|
76
|
+
new_bin = HistogramBin.new(new_value, 1)
|
77
|
+
bins.insert(index, new_bin)
|
78
|
+
return
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# If we get to here, the bin needs to be added to the end.
|
83
|
+
bins << HistogramBin.new(new_value, 1)
|
84
|
+
end
|
85
|
+
|
86
|
+
def trim
|
87
|
+
while bins.length > max_bins
|
88
|
+
trim_one
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def trim_one
|
93
|
+
minDelta = Float::MAX
|
94
|
+
minDeltaIndex = 0
|
95
|
+
|
96
|
+
# Which two bins should we merge?
|
97
|
+
bins.each_with_index do |_, index|
|
98
|
+
next if index == 0
|
99
|
+
|
100
|
+
delta = bins[index].value - bins[index - 1].value
|
101
|
+
if delta < minDelta
|
102
|
+
minDelta = delta
|
103
|
+
minDeltaIndex = index
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create the merged bin with summed count, and weighted value
|
108
|
+
mergedCount = bins[minDeltaIndex - 1].count + bins[minDeltaIndex].count
|
109
|
+
mergedValue = (
|
110
|
+
bins[minDeltaIndex - 1].value * bins[minDeltaIndex - 1].count +
|
111
|
+
bins[minDeltaIndex].value * bins[minDeltaIndex].count
|
112
|
+
) / mergedCount
|
113
|
+
|
114
|
+
mergedBin = HistogramBin.new(mergedValue, mergedCount)
|
115
|
+
|
116
|
+
# Remove the two bins we just merged together, then add the merged one
|
117
|
+
bins.slice!(minDeltaIndex - 1, 2)
|
118
|
+
bins.insert(minDeltaIndex - 1, mergedBin)
|
119
|
+
rescue => e
|
120
|
+
ScoutApm::Agent.instance.logger.info("Error in NumericHistogram#trim_one. #{e.message}, #{e.backtrace}, #{self.inspect}")
|
121
|
+
raise
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
Binary file
|
@@ -63,6 +63,7 @@ module ScoutApm
|
|
63
63
|
req.context.add_user(:ip => request.remote_ip)
|
64
64
|
req.set_headers(request.headers)
|
65
65
|
req.start_layer( ScoutApm::Layer.new("Controller", "#{controller_path}/#{action_name}") )
|
66
|
+
req.web!
|
66
67
|
|
67
68
|
begin
|
68
69
|
perform_action_without_scout_instruments(*args, &block)
|
@@ -34,7 +34,7 @@ module ScoutApm
|
|
34
34
|
req = ScoutApm::RequestManager.lookup
|
35
35
|
req.annotate_request(:uri => @request.path_info)
|
36
36
|
req.context.add_user(:ip => @request.ip)
|
37
|
-
req.set_headers(
|
37
|
+
# req.set_headers(env) # TODO: Parse headers with name HTTP_*
|
38
38
|
|
39
39
|
req.start_layer( ScoutApm::Layer.new("Controller", scout_controller_action) )
|
40
40
|
begin
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Records details about all runs of a given job.
|
2
|
+
#
|
3
|
+
# Contains:
|
4
|
+
# Queue Name
|
5
|
+
# Job Name
|
6
|
+
# Job Runtime - histogram
|
7
|
+
# Metrics collected during the run (Database, HTTP, View, etc)
|
8
|
+
module ScoutApm
|
9
|
+
class JobRecord
|
10
|
+
attr_reader :queue_name
|
11
|
+
attr_reader :job_name
|
12
|
+
attr_reader :total_time
|
13
|
+
attr_reader :exclusive_time
|
14
|
+
attr_reader :errors
|
15
|
+
attr_reader :metric_set
|
16
|
+
|
17
|
+
def initialize(queue_name, job_name, total_time, exclusive_time, errors, metrics)
|
18
|
+
@queue_name = queue_name
|
19
|
+
@job_name = job_name
|
20
|
+
|
21
|
+
@total_time = NumericHistogram.new(50)
|
22
|
+
@total_time.add(total_time)
|
23
|
+
|
24
|
+
@exclusive_time = NumericHistogram.new(50)
|
25
|
+
@exclusive_time.add(exclusive_time)
|
26
|
+
|
27
|
+
@errors = errors.to_i
|
28
|
+
|
29
|
+
@metric_set = MetricSet.new
|
30
|
+
@metric_set.absorb_all(metrics)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Modifies self and returns self, after merging in `other`.
|
34
|
+
def combine!(other)
|
35
|
+
same_job = queue_name == other.queue_name && job_name == other.job_name
|
36
|
+
raise "Mismatched Merge of Background Job" unless same_job
|
37
|
+
|
38
|
+
@errors += other.errors
|
39
|
+
@metric_set = metric_set.combine!(other.metric_set)
|
40
|
+
@total_time.combine!(other.total_time)
|
41
|
+
@exclusive_time.combine!(other.exclusive_time)
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def run_count
|
47
|
+
total_time.total
|
48
|
+
end
|
49
|
+
|
50
|
+
def metrics
|
51
|
+
metric_set.metrics
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
######################
|
56
|
+
# Hash Key interface
|
57
|
+
######################
|
58
|
+
|
59
|
+
def ==(o)
|
60
|
+
self.eql?(o)
|
61
|
+
end
|
62
|
+
|
63
|
+
def hash
|
64
|
+
h = queue_name.downcase.hash
|
65
|
+
h ^= job_name.downcase.hash
|
66
|
+
h
|
67
|
+
end
|
68
|
+
|
69
|
+
def eql?(o)
|
70
|
+
self.class == o.class &&
|
71
|
+
queue_name.downcase == o.queue_name.downcase &&
|
72
|
+
job_name.downcase == o.job_name.downcase
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -18,7 +18,10 @@ module ScoutApm
|
|
18
18
|
new_req = new_val.request_count
|
19
19
|
ScoutApm::Agent.instance.logger.debug("Merging Two reporting periods (#{old_val.timestamp.to_s}, #{new_val.timestamp.to_s}): old req #{old_req}, new req #{new_req}")
|
20
20
|
|
21
|
-
old_val.
|
21
|
+
old_val.
|
22
|
+
merge_metrics!(new_val.metrics_payload).
|
23
|
+
merge_slow_transactions!(new_val.slow_transactions).
|
24
|
+
merge_jobs!(new_val.jobs)
|
22
25
|
}
|
23
26
|
|
24
27
|
ScoutApm::Agent.instance.logger.debug("AddReportingPeriod: AfterMerge Timestamps: #{existing_data.keys.map(&:to_s).inspect}")
|
@@ -16,9 +16,9 @@ module ScoutApm
|
|
16
16
|
return nil
|
17
17
|
end
|
18
18
|
Marshal.load(dump)
|
19
|
-
rescue ArgumentError, TypeError => e
|
20
|
-
ScoutApm::Agent.instance.logger.
|
21
|
-
ScoutApm::Agent.instance.logger.debug(e.backtrace.
|
19
|
+
rescue NameError, ArgumentError, TypeError => e
|
20
|
+
ScoutApm::Agent.instance.logger.info("Unable to load data from Layaway file, resetting.")
|
21
|
+
ScoutApm::Agent.instance.logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
|
22
22
|
nil
|
23
23
|
end
|
24
24
|
|
@@ -37,7 +37,7 @@ module ScoutApm
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
rescue Errno::ENOENT, Exception => e
|
40
|
-
ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.message}]. " +
|
40
|
+
ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.class} - #{e.message}]. " +
|
41
41
|
"The user running the app must have read & write access. " +
|
42
42
|
"Change the path by setting the `data_file` key in scout_apm.yml"
|
43
43
|
)
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -30,6 +30,7 @@ module ScoutApm
|
|
30
30
|
# backtrace of where it occurred.
|
31
31
|
attr_reader :backtrace
|
32
32
|
|
33
|
+
BACKTRACE_CALLER_LIMIT = 30 # maximum number of lines to send thru for backtrace analysis
|
33
34
|
|
34
35
|
def initialize(type, name, start_time = Time.now)
|
35
36
|
@type = type
|
@@ -66,10 +67,19 @@ module ScoutApm
|
|
66
67
|
"#{type}/#{name}"
|
67
68
|
end
|
68
69
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
def capture_backtrace!
|
71
|
+
ScoutApm::Agent.instance.logger.debug { "Capturing Backtrace for Layer [#{type}/#{name}]" }
|
72
|
+
@backtrace = caller_array
|
73
|
+
end
|
74
|
+
|
75
|
+
# In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
|
76
|
+
def caller_array
|
77
|
+
# omits the first several callers which are in the ScoutAPM stack.
|
78
|
+
if ScoutApm::Environment.instance.ruby_2?
|
79
|
+
caller(3...BACKTRACE_CALLER_LIMIT)
|
80
|
+
else
|
81
|
+
caller[3...BACKTRACE_CALLER_LIMIT]
|
82
|
+
end
|
73
83
|
end
|
74
84
|
|
75
85
|
######################################
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module LayerConverters
|
3
|
+
class ConverterBase
|
4
|
+
attr_reader :walker
|
5
|
+
attr_reader :request
|
6
|
+
attr_reader :root_layer
|
7
|
+
|
8
|
+
def initialize(request)
|
9
|
+
@request = request
|
10
|
+
@root_layer = request.root_layer
|
11
|
+
@walker = DepthFirstWalker.new(root_layer)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Scope is determined by the first Controller we hit. Most of the time
|
15
|
+
# there will only be 1 anyway. But if you have a controller that calls
|
16
|
+
# another controller method, we may pick that up:
|
17
|
+
# def update
|
18
|
+
# show
|
19
|
+
# render :update
|
20
|
+
# end
|
21
|
+
def scope_layer
|
22
|
+
@scope_layer ||= walker.walk do |layer|
|
23
|
+
if layer.type == "Controller"
|
24
|
+
break layer
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|