scout_apm 2.2.0.pre3 → 2.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.markdown +147 -2
- data/Guardfile +43 -0
- data/Rakefile +2 -2
- data/ext/allocations/allocations.c +6 -0
- data/ext/allocations/extconf.rb +1 -0
- data/ext/rusage/README.md +26 -0
- data/ext/rusage/extconf.rb +5 -0
- data/ext/rusage/rusage.c +52 -0
- data/lib/scout_apm.rb +28 -15
- data/lib/scout_apm/agent.rb +89 -37
- data/lib/scout_apm/agent/logging.rb +6 -1
- data/lib/scout_apm/agent/reporting.rb +9 -6
- data/lib/scout_apm/app_server_load.rb +21 -10
- data/lib/scout_apm/attribute_arranger.rb +6 -3
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
- data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
- data/lib/scout_apm/background_recorder.rb +43 -0
- data/lib/scout_apm/background_worker.rb +19 -15
- data/lib/scout_apm/config.rb +138 -28
- data/lib/scout_apm/db_query_metric_set.rb +80 -0
- data/lib/scout_apm/db_query_metric_stats.rb +102 -0
- data/lib/scout_apm/debug.rb +37 -0
- data/lib/scout_apm/environment.rb +22 -15
- data/lib/scout_apm/git_revision.rb +51 -0
- data/lib/scout_apm/histogram.rb +11 -2
- data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
- data/lib/scout_apm/instant/middleware.rb +196 -54
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
- data/lib/scout_apm/instruments/action_view.rb +49 -0
- data/lib/scout_apm/instruments/active_record.rb +127 -3
- data/lib/scout_apm/instruments/grape.rb +4 -3
- data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
- data/lib/scout_apm/instruments/mongoid.rb +24 -3
- data/lib/scout_apm/instruments/net_http.rb +7 -2
- data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
- data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
- data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
- data/lib/scout_apm/instruments/resque.rb +40 -0
- data/lib/scout_apm/layaway.rb +67 -28
- data/lib/scout_apm/layer.rb +19 -59
- data/lib/scout_apm/layer_children_set.rb +77 -0
- data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
- data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
- data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
- data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
- data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
- data/lib/scout_apm/layer_converters/histograms.rb +14 -0
- data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
- data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
- data/lib/scout_apm/limited_layer.rb +126 -0
- data/lib/scout_apm/metric_meta.rb +0 -5
- data/lib/scout_apm/metric_set.rb +9 -1
- data/lib/scout_apm/metric_stats.rb +7 -8
- data/lib/scout_apm/rack.rb +26 -0
- data/lib/scout_apm/remote/message.rb +23 -0
- data/lib/scout_apm/remote/recorder.rb +57 -0
- data/lib/scout_apm/remote/router.rb +49 -0
- data/lib/scout_apm/remote/server.rb +58 -0
- data/lib/scout_apm/reporter.rb +51 -15
- data/lib/scout_apm/request_histograms.rb +4 -0
- data/lib/scout_apm/request_manager.rb +2 -1
- data/lib/scout_apm/scored_item_set.rb +7 -0
- data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
- data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
- data/lib/scout_apm/server_integrations/puma.rb +5 -2
- data/lib/scout_apm/slow_job_policy.rb +1 -10
- data/lib/scout_apm/slow_job_record.rb +6 -1
- data/lib/scout_apm/slow_request_policy.rb +1 -10
- data/lib/scout_apm/slow_transaction.rb +20 -2
- data/lib/scout_apm/store.rb +66 -12
- data/lib/scout_apm/synchronous_recorder.rb +26 -0
- data/lib/scout_apm/tracked_request.rb +136 -71
- data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
- data/lib/scout_apm/utils/gzip_helper.rb +24 -0
- data/lib/scout_apm/utils/numbers.rb +14 -0
- data/lib/scout_apm/utils/scm.rb +14 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +5 -4
- data/test/test_helper.rb +18 -0
- data/test/unit/config_test.rb +59 -8
- data/test/unit/db_query_metric_set_test.rb +56 -0
- data/test/unit/db_query_metric_stats_test.rb +113 -0
- data/test/unit/git_revision_test.rb +15 -0
- data/test/unit/histogram_test.rb +14 -0
- data/test/unit/instruments/net_http_test.rb +21 -0
- data/test/unit/instruments/percentile_sampler_test.rb +137 -0
- data/test/unit/layaway_test.rb +20 -0
- data/test/unit/layer_children_set_test.rb +88 -0
- data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
- data/test/unit/layer_converters/metric_converter_test.rb +22 -0
- data/test/unit/layer_converters/stubs.rb +33 -0
- data/test/unit/limited_layer_test.rb +53 -0
- data/test/unit/remote/test_message.rb +13 -0
- data/test/unit/remote/test_router.rb +33 -0
- data/test/unit/remote/test_server.rb +15 -0
- data/test/unit/serializers/payload_serializer_test.rb +3 -12
- data/test/unit/store_test.rb +66 -0
- data/test/unit/test_tracked_request.rb +87 -0
- data/test/unit/utils/active_record_metric_name_test.rb +8 -0
- data/test/unit/utils/backtrace_parser_test.rb +5 -0
- data/test/unit/utils/numbers_test.rb +15 -0
- data/test/unit/utils/scm.rb +17 -0
- metadata +125 -30
- data/ext/stacks/extconf.rb +0 -37
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -811
- data/lib/scout_apm/capacity.rb +0 -57
- data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
- data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
- data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
- data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
- data/lib/scout_apm/instruments/delayed_job.rb +0 -57
- data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -87
- data/tester.rb +0 -53
data/lib/scout_apm/capacity.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
# Encapsulates logic for determining capacity utilization of the Ruby processes.
|
2
|
-
module ScoutApm
|
3
|
-
class Capacity
|
4
|
-
attr_reader :processing_start_time, :accumulated_time, :transaction_entry_time
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@processing_start_time = Time.now
|
8
|
-
@lock ||= Mutex.new # the transaction_entry_time could be modified while processing a request or when #process is called.
|
9
|
-
@accumulated_time = 0.0
|
10
|
-
end
|
11
|
-
|
12
|
-
# Called when a transaction is traced.
|
13
|
-
def start_transaction!
|
14
|
-
@lock.synchronize do
|
15
|
-
@transaction_entry_time = Time.now
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Called when a transaction completes to record its time used.
|
20
|
-
def finish_transaction!
|
21
|
-
@lock.synchronize do
|
22
|
-
if transaction_entry_time
|
23
|
-
@accumulated_time += (Time.now - transaction_entry_time).to_f
|
24
|
-
else
|
25
|
-
ScoutApm::Agent.instance.logger.warn "No transaction entry time. Not recording capacity metrics for transaction."
|
26
|
-
end
|
27
|
-
@transaction_entry_time = nil
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Ran when sending metrics to server. Reports capacity usage metrics.
|
32
|
-
def process
|
33
|
-
process_time = Time.now
|
34
|
-
ScoutApm::Agent.instance.logger.debug "Processing capacity usage for [#{@processing_start_time}] to [#{process_time}]. Time Spent: #{@accumulated_time}."
|
35
|
-
@lock.synchronize do
|
36
|
-
time_spent = @accumulated_time
|
37
|
-
@accumulated_time = 0.0
|
38
|
-
# If a transaction is still running, capture its running time up to now and
|
39
|
-
# reset the +transaction_entry_time+ to now.
|
40
|
-
if @transaction_entry_time
|
41
|
-
time_spent += (process_time - @transaction_entry_time).to_f
|
42
|
-
ScoutApm::Agent.instance.logger.debug "A transaction is running while calculating capacity. Start time: [#{transaction_entry_time}]. Will update the entry time to [#{process_time}]."
|
43
|
-
@transaction_entry_time = process_time # prevent from over-counting capacity usage. update the transaction start time to now.
|
44
|
-
end
|
45
|
-
time_spent = 0.0 if time_spent < 0.0
|
46
|
-
|
47
|
-
window = (process_time - processing_start_time).to_f # time period we are evaulating capacity usage.
|
48
|
-
window = 1.0 if window <= 0.0 # prevent divide-by-zero if clock adjusted.
|
49
|
-
capacity = time_spent / window
|
50
|
-
ScoutApm::Agent.instance.logger.debug "Instance/Capacity: #{capacity}"
|
51
|
-
ScoutApm::Agent.instance.store.track_one!("Instance", "Capacity", capacity)
|
52
|
-
|
53
|
-
@processing_start_time = process_time
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
namespace :scout_apm do
|
2
|
-
namespace :deploy do
|
3
|
-
task :starting do
|
4
|
-
# Warn if missing scout apm deploy creds?
|
5
|
-
end
|
6
|
-
task :finished do
|
7
|
-
ScoutApm::Agent.instance.deploy_integration.report
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
after 'deploy:finished', 'scout_apm:deploy:finished'
|
@@ -1,83 +0,0 @@
|
|
1
|
-
require 'scout_apm'
|
2
|
-
|
3
|
-
module ScoutApm
|
4
|
-
module DeployIntegrations
|
5
|
-
class Capistrano2
|
6
|
-
attr_reader :logger
|
7
|
-
|
8
|
-
def initialize(logger)
|
9
|
-
@logger = logger
|
10
|
-
@cap = defined?(Capistrano::Configuration) ? ObjectSpace.each_object(Capistrano::Configuration).map.first : nil rescue nil
|
11
|
-
end
|
12
|
-
|
13
|
-
def name
|
14
|
-
:capistrano_2
|
15
|
-
end
|
16
|
-
|
17
|
-
def version
|
18
|
-
present? ? Capistrano::VERSION : nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def present?
|
22
|
-
if !@cap.nil? && @cap.is_a?(Capistrano::Configuration)
|
23
|
-
require 'capistrano/version'
|
24
|
-
defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 2.0').match?('', Capistrano::VERSION.to_s)
|
25
|
-
else
|
26
|
-
return false
|
27
|
-
end
|
28
|
-
return true
|
29
|
-
rescue
|
30
|
-
return false
|
31
|
-
end
|
32
|
-
|
33
|
-
def install
|
34
|
-
logger.debug "Initializing Capistrano2 Deploy Integration."
|
35
|
-
@cap.load File.expand_path("../capistrano_2.cap", __FILE__)
|
36
|
-
end
|
37
|
-
|
38
|
-
def root
|
39
|
-
'.'
|
40
|
-
end
|
41
|
-
|
42
|
-
def env
|
43
|
-
@cap.fetch(:stage)
|
44
|
-
end
|
45
|
-
|
46
|
-
def found?
|
47
|
-
true
|
48
|
-
end
|
49
|
-
|
50
|
-
def report
|
51
|
-
if reporter.can_report?
|
52
|
-
data = deploy_data
|
53
|
-
logger.debug "Sending deploy hook data: #{data}"
|
54
|
-
payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
|
55
|
-
reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
|
56
|
-
else
|
57
|
-
logger.warn "Unable to post deploy hook data"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def reporter
|
62
|
-
@reporter ||= ScoutApm::Reporter.new(:deploy_hook, ScoutApm::Agent.instance.config, @logger)
|
63
|
-
end
|
64
|
-
|
65
|
-
def deploy_data
|
66
|
-
{:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
|
67
|
-
end
|
68
|
-
|
69
|
-
def branch
|
70
|
-
@cap.fetch(:branch)
|
71
|
-
end
|
72
|
-
|
73
|
-
def current_revision
|
74
|
-
@cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
|
75
|
-
end
|
76
|
-
|
77
|
-
def deployed_by
|
78
|
-
ScoutApm::Agent.instance.config.value('deployed_by')
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
namespace :scout_apm do
|
2
|
-
namespace :deploy do
|
3
|
-
task :starting do
|
4
|
-
# Warn if missing scout apm deploy creds?
|
5
|
-
end
|
6
|
-
task :finished do
|
7
|
-
ScoutApm::Agent.instance.deploy_integration.report
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
after 'deploy:finished', 'scout_apm:deploy:finished'
|
@@ -1,88 +0,0 @@
|
|
1
|
-
require 'scout_apm'
|
2
|
-
|
3
|
-
module ScoutApm
|
4
|
-
module DeployIntegrations
|
5
|
-
class Capistrano3
|
6
|
-
attr_reader :logger
|
7
|
-
|
8
|
-
def initialize(logger)
|
9
|
-
@logger = logger
|
10
|
-
@cap = Rake.application rescue nil
|
11
|
-
end
|
12
|
-
|
13
|
-
def name
|
14
|
-
:capistrano_3
|
15
|
-
end
|
16
|
-
|
17
|
-
def version
|
18
|
-
present? ? Capistrano::VERSION : nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def present?
|
22
|
-
if !@cap.nil? && @cap.is_a?(Capistrano::Application)
|
23
|
-
require 'capistrano/version'
|
24
|
-
defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 3.0').match?('', Capistrano::VERSION.to_s)
|
25
|
-
else
|
26
|
-
return false
|
27
|
-
end
|
28
|
-
rescue
|
29
|
-
return false
|
30
|
-
end
|
31
|
-
|
32
|
-
def install
|
33
|
-
logger.debug "Initializing Capistrano3 Deploy Integration."
|
34
|
-
load File.expand_path("../capistrano_3.cap", __FILE__)
|
35
|
-
end
|
36
|
-
|
37
|
-
def root
|
38
|
-
'.'
|
39
|
-
end
|
40
|
-
|
41
|
-
def env
|
42
|
-
@cap.fetch(:stage).to_s
|
43
|
-
end
|
44
|
-
|
45
|
-
def found?
|
46
|
-
true
|
47
|
-
end
|
48
|
-
|
49
|
-
def report
|
50
|
-
if reporter.can_report?
|
51
|
-
data = deploy_data
|
52
|
-
logger.debug "Sending deploy hook data: #{data}"
|
53
|
-
payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
|
54
|
-
reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
|
55
|
-
else
|
56
|
-
logger.warn "Unable to post deploy hook data"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def reporter
|
61
|
-
config = if env == ''
|
62
|
-
ScoutApm::Agent.instance.config
|
63
|
-
else
|
64
|
-
ScoutApm::Config.with_file(nil, {:file => { :environment => env }}) # instantiate our own config, with an overridden environment for the deploy-to app name instead of deploy-from app name)
|
65
|
-
end
|
66
|
-
|
67
|
-
@reporter ||= ScoutApm::Reporter.new(:deploy_hook, config, @logger)
|
68
|
-
end
|
69
|
-
|
70
|
-
def deploy_data
|
71
|
-
{:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
|
72
|
-
end
|
73
|
-
|
74
|
-
def branch
|
75
|
-
@cap.fetch(:branch)
|
76
|
-
end
|
77
|
-
|
78
|
-
def current_revision
|
79
|
-
@cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
|
80
|
-
end
|
81
|
-
|
82
|
-
def deployed_by
|
83
|
-
ScoutApm::Agent.instance.config.value('deployed_by')
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module ScoutApm
|
2
|
-
module Instruments
|
3
|
-
class DelayedJob
|
4
|
-
attr_reader :logger
|
5
|
-
|
6
|
-
def initialize(logger=ScoutApm::Agent.instance.logger)
|
7
|
-
@logger = logger
|
8
|
-
@installed = false
|
9
|
-
end
|
10
|
-
|
11
|
-
def installed?
|
12
|
-
@installed
|
13
|
-
end
|
14
|
-
|
15
|
-
def install
|
16
|
-
@installed = true
|
17
|
-
if defined?(::Delayed::Worker)
|
18
|
-
::Delayed::Worker.class_eval do
|
19
|
-
include ScoutApm::Tracer
|
20
|
-
include ScoutApm::Instruments::DelayedJobInstruments
|
21
|
-
alias run_without_scout_instruments run
|
22
|
-
alias run run_with_scout_instruments
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
module DelayedJobInstruments
|
29
|
-
def run_with_scout_instruments(job)
|
30
|
-
scout_method_name = method_from_handler(job.handler)
|
31
|
-
queue = job.queue
|
32
|
-
latency = (Time.now.to_f - job.created_at.to_f) * 1000
|
33
|
-
|
34
|
-
ScoutApm::Agent.instance.store.track_one!("Queue", queue, 0, {:extra_metrics => {:latency => latency}})
|
35
|
-
req = ScoutApm::RequestManager.lookup
|
36
|
-
req.job!
|
37
|
-
req.start_layer( ScoutApm::Layer.new("Job", scout_method_name) )
|
38
|
-
|
39
|
-
begin
|
40
|
-
run_without_scout_instruments(job)
|
41
|
-
rescue
|
42
|
-
req.error!
|
43
|
-
raise
|
44
|
-
ensure
|
45
|
-
req.stop_layer
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def method_from_handler(handler)
|
50
|
-
job_handler = YAML.load(handler)
|
51
|
-
klass = job_handler.object.name
|
52
|
-
method = job_handler.method_name
|
53
|
-
"#{klass}##{method}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# Serialize & deserialize deploy data up to the APM server
|
2
|
-
module ScoutApm
|
3
|
-
module Serializers
|
4
|
-
class DeploySerializer
|
5
|
-
HTTP_HEADERS = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
6
|
-
|
7
|
-
def self.serialize(data)
|
8
|
-
URI.encode_www_form(data)
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.deserialize(data)
|
12
|
-
Marshal.load(data)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,312 +0,0 @@
|
|
1
|
-
# Takes in a ton of traces. Structure is a several nested arrays:
|
2
|
-
# [ # Traces
|
3
|
-
# [ # Trace
|
4
|
-
# [file,line,method,klass] # TraceLine (raw)
|
5
|
-
# ]
|
6
|
-
# ]
|
7
|
-
#
|
8
|
-
# Cleans them
|
9
|
-
# Merges them by gem/app
|
10
|
-
#
|
11
|
-
module ScoutApm
|
12
|
-
class TraceSet
|
13
|
-
# A TraceCube object which is a glorified hash of { Trace -> Count }. Used to
|
14
|
-
# collect up the count of each unique trace we've seen
|
15
|
-
attr_reader :cube
|
16
|
-
|
17
|
-
# Allow layer to push values in
|
18
|
-
attr_accessor :raw_traces
|
19
|
-
attr_accessor :skipped_in_gc
|
20
|
-
attr_accessor :skipped_in_handler
|
21
|
-
attr_accessor :skipped_in_job_registered
|
22
|
-
attr_accessor :skipped_in_not_running
|
23
|
-
|
24
|
-
def initialize
|
25
|
-
@raw_traces = []
|
26
|
-
@cube = TraceCube.new
|
27
|
-
end
|
28
|
-
|
29
|
-
# We need to know what the "Start" of this trace is. An untrimmed trace generally is:
|
30
|
-
#
|
31
|
-
# Gem
|
32
|
-
# Gem
|
33
|
-
# App
|
34
|
-
# App
|
35
|
-
# App <---- set root_class of this.
|
36
|
-
# Rails
|
37
|
-
# Rails
|
38
|
-
def set_root_class(klass_name)
|
39
|
-
@root_klass = klass_name.to_s
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_a
|
43
|
-
res = []
|
44
|
-
create_cube!
|
45
|
-
@cube.each do |(trace, count)|
|
46
|
-
res << [trace.to_a, count]
|
47
|
-
end
|
48
|
-
|
49
|
-
res
|
50
|
-
end
|
51
|
-
|
52
|
-
def as_json
|
53
|
-
res = []
|
54
|
-
create_cube!
|
55
|
-
@cube.each do |(trace, count)|
|
56
|
-
res << [trace.as_json, count]
|
57
|
-
end
|
58
|
-
|
59
|
-
res
|
60
|
-
end
|
61
|
-
|
62
|
-
def create_cube!
|
63
|
-
while raw_trace = @raw_traces.shift
|
64
|
-
clean_trace = ScoutApm::CleanTrace.new(raw_trace, @root_klass)
|
65
|
-
@cube << clean_trace
|
66
|
-
end
|
67
|
-
@raw_traces = []
|
68
|
-
end
|
69
|
-
|
70
|
-
def total_count
|
71
|
-
create_cube!
|
72
|
-
cube.inject(0) do |sum, (_, count)|
|
73
|
-
sum + count
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def inspect
|
78
|
-
create_cube!
|
79
|
-
cube.map do |(trace, count)|
|
80
|
-
"\t#{count} -- #{trace.first.klass}##{trace.first.method}\n\t\t#{trace.to_a[1].try(:klass)}##{trace.to_a[1].try(:method)}"
|
81
|
-
end.join("\n")
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# A trace is a list of individual lines, where one called another, forming a backtrace.
|
86
|
-
# Each line is made up of File, Line #, Klass, Method
|
87
|
-
#
|
88
|
-
# For the purpouses of this class:
|
89
|
-
# "Top" of the trace means the currently-running method.
|
90
|
-
# "Bottom" means the root of the call tree, from program start into rails and so on.
|
91
|
-
#
|
92
|
-
# This class trims off top and bottom to get a the meat of the trace
|
93
|
-
class CleanTrace
|
94
|
-
include Enumerable
|
95
|
-
|
96
|
-
attr_reader :lines
|
97
|
-
|
98
|
-
def initialize(raw_trace, root_klass=nil)
|
99
|
-
@lines = Array(raw_trace).map {|frame, lineno| TraceLine.new(frame, lineno)}
|
100
|
-
@root_klass = root_klass
|
101
|
-
|
102
|
-
# A trace has interesting data in the middle of it, since normally it'll go
|
103
|
-
# RailsCode -> App Code -> Gem Code.
|
104
|
-
#
|
105
|
-
# So we drop the code that leads up to your app, since a deep trace that
|
106
|
-
# always says that you went through middleware and the rails router doesn't
|
107
|
-
# help diagnose issues.
|
108
|
-
drop_below_app
|
109
|
-
|
110
|
-
# Then we drop most of the Gem Code, since you didn't write it, and in the
|
111
|
-
# vast majority of the cases, the time spent there is because your app code
|
112
|
-
# asked, not because of inherent issues with the gem. For instance, if you
|
113
|
-
# fire off a slow query to a database gem, you probably want to be
|
114
|
-
# optimizing the query, not trying to make the database gem faster.
|
115
|
-
drop_above_app
|
116
|
-
end
|
117
|
-
|
118
|
-
# Iterate starting at END of array until a controller line is found. Pop off at that index - 1.
|
119
|
-
def drop_below_app
|
120
|
-
pops = 0
|
121
|
-
index = lines.size - 1 # last index, not size.
|
122
|
-
|
123
|
-
while index >= 0 && !lines[index].controller?(@root_klass)
|
124
|
-
index -= 1
|
125
|
-
pops += 1
|
126
|
-
end
|
127
|
-
|
128
|
-
lines.pop(pops)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Find the closest mention of the application code from the currently-running method.
|
132
|
-
# Then adjust by 1 if possible to capture the "first" line
|
133
|
-
def drop_above_app
|
134
|
-
ai = @lines.find_index(&:app?)
|
135
|
-
if ai
|
136
|
-
ai -= 1 if ai > 0
|
137
|
-
@lines = @lines[ai .. -1]
|
138
|
-
else
|
139
|
-
@lines = [] # No app line in backtrace, nothing to show?
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def each
|
144
|
-
@lines.each { |line| yield line }
|
145
|
-
end
|
146
|
-
|
147
|
-
def empty?
|
148
|
-
@lines.empty?
|
149
|
-
end
|
150
|
-
|
151
|
-
def as_json
|
152
|
-
@lines.map { |line| line.as_json }
|
153
|
-
end
|
154
|
-
|
155
|
-
###############################
|
156
|
-
# Hash Key interface
|
157
|
-
def hash
|
158
|
-
@lines.hash
|
159
|
-
end
|
160
|
-
|
161
|
-
def eql?(other)
|
162
|
-
@lines.eql?(other.lines)
|
163
|
-
end
|
164
|
-
###############################
|
165
|
-
end
|
166
|
-
|
167
|
-
class TraceLine
|
168
|
-
# An opaque C object, only call Stacks#frame_* methods on this.
|
169
|
-
attr_reader :frame
|
170
|
-
|
171
|
-
# The line number. This doesn't appear to be obtainable from the frame itself
|
172
|
-
attr_reader :lineno
|
173
|
-
|
174
|
-
def initialize(frame, lineno)
|
175
|
-
@frame = frame
|
176
|
-
@lineno = lineno
|
177
|
-
end
|
178
|
-
|
179
|
-
# Returns the name of the last gem in the line
|
180
|
-
def gem_name
|
181
|
-
@gem_name ||= begin
|
182
|
-
r = %r{\/gems/(.*?)/}.freeze
|
183
|
-
results = file.scan(r)
|
184
|
-
results[-1][0] # Scan will return a nested array, so extract out that nesting
|
185
|
-
rescue
|
186
|
-
nil
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def stdlib_name
|
191
|
-
@stdlib_name ||= begin
|
192
|
-
r = %r{#{Regexp.escape(RbConfig::TOPDIR)}/(.*?)}.freeze
|
193
|
-
results = file.scan(r)
|
194
|
-
results[-1][0] # Scan will return a nested array, so extract out that nesting
|
195
|
-
rescue
|
196
|
-
nil
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def file
|
201
|
-
ScoutApm::Instruments::Stacks.frame_file(frame)
|
202
|
-
end
|
203
|
-
|
204
|
-
|
205
|
-
# If we ever want to get the "first line of the method" - ScoutApm::Instruments::Stacks.frame_lineno(frame)
|
206
|
-
def line
|
207
|
-
lineno
|
208
|
-
end
|
209
|
-
|
210
|
-
def klass
|
211
|
-
ScoutApm::Instruments::Stacks.frame_klass(frame)
|
212
|
-
end
|
213
|
-
|
214
|
-
def method
|
215
|
-
ScoutApm::Instruments::Stacks.frame_method(frame)
|
216
|
-
end
|
217
|
-
|
218
|
-
def gem?
|
219
|
-
!!gem_name
|
220
|
-
end
|
221
|
-
|
222
|
-
def stdlib?
|
223
|
-
!!stdlib_name
|
224
|
-
end
|
225
|
-
|
226
|
-
def app?
|
227
|
-
r = %r|^#{Regexp.escape(ScoutApm::Environment.instance.root.to_s)}/|.freeze
|
228
|
-
result = !gem_name && !stdlib_name && file =~ r
|
229
|
-
!!result # coerce to a bool
|
230
|
-
end
|
231
|
-
|
232
|
-
def trim_file(file_path)
|
233
|
-
return if file_path.nil?
|
234
|
-
if gem?
|
235
|
-
r = %r{.*gems/.*?/}.freeze
|
236
|
-
file_path.sub(r, "/")
|
237
|
-
elsif stdlib?
|
238
|
-
file_path.sub(RbConfig::TOPDIR, '')
|
239
|
-
elsif app?
|
240
|
-
file_path.sub(ScoutApm::Environment.instance.root.to_s, '')
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# If root_klass is provided, just see if this is exactly that class. If not,
|
245
|
-
# fall back on "is this in the app"
|
246
|
-
def controller?(root_klass)
|
247
|
-
return false if klass.nil? # main function doesn't have a file associated
|
248
|
-
|
249
|
-
if root_klass
|
250
|
-
klass == root_klass
|
251
|
-
else
|
252
|
-
app?
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
def formatted_to_s
|
257
|
-
"#{stdlib_name} #{klass}##{method} -- #{file}:#{line}"
|
258
|
-
end
|
259
|
-
|
260
|
-
def as_json
|
261
|
-
[ trim_file(file), line, klass, method, app?, gem_name, stdlib_name ]
|
262
|
-
end
|
263
|
-
|
264
|
-
###############################
|
265
|
-
# Hash Key interface
|
266
|
-
|
267
|
-
def hash
|
268
|
-
# Note that this does not include line number. It caused a few situations
|
269
|
-
# where we had a bunch of time spent in one method, but across a few lines,
|
270
|
-
# we decided that it made sense to group them together.
|
271
|
-
file.hash ^ method.hash ^ klass.hash
|
272
|
-
end
|
273
|
-
|
274
|
-
def eql?(other)
|
275
|
-
file == other.file &&
|
276
|
-
method == other.method &&
|
277
|
-
klass == other.klass
|
278
|
-
end
|
279
|
-
|
280
|
-
###############################
|
281
|
-
end
|
282
|
-
|
283
|
-
# Collects clean traces and counts how many of each we have.
|
284
|
-
class TraceCube
|
285
|
-
include Enumerable
|
286
|
-
|
287
|
-
attr_reader :traces
|
288
|
-
attr_reader :total_count
|
289
|
-
|
290
|
-
def initialize
|
291
|
-
@traces = Hash.new{ |h,k| h[k] = 0 }
|
292
|
-
@total_count = 0
|
293
|
-
end
|
294
|
-
|
295
|
-
def <<(clean_trace)
|
296
|
-
@total_count += 1
|
297
|
-
@traces[clean_trace] += 1
|
298
|
-
end
|
299
|
-
|
300
|
-
# Yields two element array, the trace and the count of that trace
|
301
|
-
# In descending order of count.
|
302
|
-
def each
|
303
|
-
@traces
|
304
|
-
.to_a
|
305
|
-
.each { |(trace, count)|
|
306
|
-
next if trace.empty?
|
307
|
-
yield [trace, count]
|
308
|
-
}
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|