scout_apm 0.1.17 → 0.1.18.stackprof4
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 +0 -4
- data/lib/scout_apm.rb +21 -0
- data/lib/scout_apm/agent.rb +9 -0
- data/lib/scout_apm/config.rb +17 -4
- data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
- data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
- data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
- data/lib/scout_apm/deploy_integrations/capistrano_3.rb +82 -0
- data/lib/scout_apm/environment.rb +20 -3
- data/lib/scout_apm/instruments/action_controller_rails_3.rb +22 -10
- data/lib/scout_apm/reporter.rb +21 -2
- data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
- data/lib/scout_apm/slow_transaction.rb +42 -29
- data/lib/scout_apm/stackprof_tree_collapser.rb +218 -0
- data/lib/scout_apm/store.rb +15 -7
- data/lib/scout_apm/tracer.rb +15 -3
- data/lib/scout_apm/utils/fake_stack_prof.rb +36 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +2 -0
- metadata +31 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7040acd30e2e30aceb89d48e43c699e007d87ea5
|
4
|
+
data.tar.gz: 76222f981eb632a50948c4f765ad387c671bbf80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 811ca92ad91009f8e3577caa8641a7b8c6e189bf2460b871f24c1d98d9e5a8b75376194391729aa42d47e3a47e9bf138dfcb34f0cd316f346281b870fd50e902
|
7
|
+
data.tar.gz: 5b0e5825e3d97e4ba7ce950792890bc5318be6e5fb23d60e6ad4c8bbeac6f1599a14d2f8ecf721c44cc56f046b711b090e322f8f87133dadf912220eeef7f613
|
data/CHANGELOG.markdown
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
end
|
3
3
|
|
4
|
+
#####################################
|
5
|
+
# Ruby StdLibrary Requires
|
6
|
+
#####################################
|
4
7
|
require 'cgi'
|
5
8
|
require 'logger'
|
6
9
|
require 'net/http'
|
@@ -10,6 +13,18 @@ require 'socket'
|
|
10
13
|
require 'yaml'
|
11
14
|
require 'thread'
|
12
15
|
|
16
|
+
#####################################
|
17
|
+
# Gem Requires
|
18
|
+
#####################################
|
19
|
+
begin
|
20
|
+
require 'stackprof'
|
21
|
+
rescue LoadError
|
22
|
+
require 'scout_apm/utils/fake_stack_prof'
|
23
|
+
end
|
24
|
+
|
25
|
+
#####################################
|
26
|
+
# Internal Requires
|
27
|
+
#####################################
|
13
28
|
require 'scout_apm/version'
|
14
29
|
|
15
30
|
require 'scout_apm/server_integrations/passenger'
|
@@ -25,6 +40,9 @@ require 'scout_apm/framework_integrations/rails_3_or_4'
|
|
25
40
|
require 'scout_apm/framework_integrations/sinatra'
|
26
41
|
require 'scout_apm/framework_integrations/ruby'
|
27
42
|
|
43
|
+
require 'scout_apm/deploy_integrations/capistrano_3'
|
44
|
+
#require 'scout_apm/deploy_integrations/capistrano_2'
|
45
|
+
|
28
46
|
require 'scout_apm/instruments/net_http'
|
29
47
|
require 'scout_apm/instruments/moped'
|
30
48
|
require 'scout_apm/instruments/mongoid'
|
@@ -41,6 +59,7 @@ require 'scout_apm/utils/sql_sanitizer'
|
|
41
59
|
require 'scout_apm/utils/null_logger'
|
42
60
|
require 'scout_apm/utils/installed_gems'
|
43
61
|
require 'scout_apm/utils/time'
|
62
|
+
|
44
63
|
require 'scout_apm/config'
|
45
64
|
require 'scout_apm/environment'
|
46
65
|
require 'scout_apm/agent'
|
@@ -56,12 +75,14 @@ require 'scout_apm/stack_item'
|
|
56
75
|
require 'scout_apm/store'
|
57
76
|
require 'scout_apm/tracer'
|
58
77
|
require 'scout_apm/context'
|
78
|
+
require 'scout_apm/stackprof_tree_collapser'
|
59
79
|
require 'scout_apm/slow_transaction'
|
60
80
|
require 'scout_apm/capacity'
|
61
81
|
|
62
82
|
require 'scout_apm/serializers/payload_serializer'
|
63
83
|
require 'scout_apm/serializers/directive_serializer'
|
64
84
|
require 'scout_apm/serializers/app_server_load_serializer'
|
85
|
+
require 'scout_apm/serializers/deploy_serializer'
|
65
86
|
|
66
87
|
if defined?(Rails) and Rails.respond_to?(:version) and Rails.version >= '3'
|
67
88
|
module ScoutApm
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -78,6 +78,11 @@ module ScoutApm
|
|
78
78
|
init_logger
|
79
79
|
logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
|
80
80
|
|
81
|
+
if environment.deploy_integration
|
82
|
+
logger.info "Starting monitoring for [#{environment.deploy_integration.name}]]."
|
83
|
+
return environment.deploy_integration.install
|
84
|
+
end
|
85
|
+
|
81
86
|
return false unless preconditions_met?
|
82
87
|
|
83
88
|
@started = true
|
@@ -197,5 +202,9 @@ module ScoutApm
|
|
197
202
|
@installed_instruments << instance
|
198
203
|
instance.install
|
199
204
|
end
|
205
|
+
|
206
|
+
def deploy_integration
|
207
|
+
environment.deploy_integration
|
208
|
+
end
|
200
209
|
end
|
201
210
|
end
|
data/lib/scout_apm/config.rb
CHANGED
@@ -21,6 +21,7 @@ module ScoutApm
|
|
21
21
|
DEFAULTS = {
|
22
22
|
'host' => 'https://apm.scoutapp.com',
|
23
23
|
'log_level' => 'info',
|
24
|
+
'stackprof_interval' => 20000 # microseconds, 1000 = 1 millisecond, so 20k == 20 milliseconds
|
24
25
|
}.freeze
|
25
26
|
|
26
27
|
def initialize(config_path = nil)
|
@@ -30,8 +31,16 @@ module ScoutApm
|
|
30
31
|
# Fetch a config value.
|
31
32
|
# It first attempts to fetch an ENV var prefixed with 'SCOUT_',
|
32
33
|
# then from the settings file.
|
33
|
-
|
34
|
-
|
34
|
+
#
|
35
|
+
# If you set env_only, then it will not attempt to read the config file at
|
36
|
+
# all, and only read off the ENV var this is useful to break a loop during
|
37
|
+
# boot, where we needed an option to set the application root.
|
38
|
+
def value(key, env_only=false)
|
39
|
+
value = ENV['SCOUT_'+key.upcase]
|
40
|
+
if !value && !env_only
|
41
|
+
value = setting(key)
|
42
|
+
end
|
43
|
+
|
35
44
|
value.to_s.strip.length.zero? ? nil : value
|
36
45
|
end
|
37
46
|
|
@@ -45,8 +54,12 @@ module ScoutApm
|
|
45
54
|
File.expand_path(config_path)
|
46
55
|
end
|
47
56
|
|
48
|
-
def
|
49
|
-
|
57
|
+
def setting(key)
|
58
|
+
settings[key] || settings(true)[key]
|
59
|
+
end
|
60
|
+
|
61
|
+
def settings(try_reload=false)
|
62
|
+
(@settings.nil? || try_reload) ? @settings = load_file : @settings
|
50
63
|
end
|
51
64
|
|
52
65
|
def config_environment
|
@@ -0,0 +1,12 @@
|
|
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'
|
@@ -0,0 +1,83 @@
|
|
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
|
@@ -0,0 +1,12 @@
|
|
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'
|
@@ -0,0 +1,82 @@
|
|
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
|
+
@reporter ||= ScoutApm::Reporter.new(:deploy_hook, ScoutApm::Agent.instance.config, @logger)
|
62
|
+
end
|
63
|
+
|
64
|
+
def deploy_data
|
65
|
+
{:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
|
66
|
+
end
|
67
|
+
|
68
|
+
def branch
|
69
|
+
@cap.fetch(:branch)
|
70
|
+
end
|
71
|
+
|
72
|
+
def current_revision
|
73
|
+
@cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
|
74
|
+
end
|
75
|
+
|
76
|
+
def deployed_by
|
77
|
+
ScoutApm::Agent.instance.config.value('deployed_by')
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -24,8 +24,13 @@ module ScoutApm
|
|
24
24
|
ScoutApm::FrameworkIntegrations::Ruby.new, # Fallback if none match
|
25
25
|
]
|
26
26
|
|
27
|
+
DEPLOY_INTEGRATIONS = [
|
28
|
+
ScoutApm::DeployIntegrations::Capistrano3.new(Logger.new(STDOUT)),
|
29
|
+
# ScoutApm::DeployIntegrations::Capistrano2.new(Logger.new(STDOUT)),
|
30
|
+
]
|
31
|
+
|
27
32
|
def env
|
28
|
-
@env ||= framework_integration.env
|
33
|
+
@env ||= deploy_integration? ? deploy_integration.env : framework_integration.env
|
29
34
|
end
|
30
35
|
|
31
36
|
def framework
|
@@ -58,10 +63,14 @@ module ScoutApm
|
|
58
63
|
end
|
59
64
|
|
60
65
|
def root
|
61
|
-
|
66
|
+
return deploy_integration.root if deploy_integration
|
67
|
+
framework_root
|
68
|
+
end
|
69
|
+
|
70
|
+
def framework_root
|
71
|
+
if override_root = Agent.instance.config.value("application_root", true)
|
62
72
|
return override_root
|
63
73
|
end
|
64
|
-
|
65
74
|
if framework == :rails
|
66
75
|
RAILS_ROOT.to_s
|
67
76
|
elsif framework == :rails3_or_4
|
@@ -102,6 +111,14 @@ module ScoutApm
|
|
102
111
|
app_server_integration.forking?
|
103
112
|
end
|
104
113
|
|
114
|
+
def deploy_integration
|
115
|
+
@deploy_integration ||= DEPLOY_INTEGRATIONS.detect{ |integration| integration.present? }
|
116
|
+
end
|
117
|
+
|
118
|
+
def deploy_integration?
|
119
|
+
!@deploy_integration.nil?
|
120
|
+
end
|
121
|
+
|
105
122
|
### ruby checks
|
106
123
|
|
107
124
|
def rubinius?
|
@@ -27,12 +27,25 @@ module ScoutApm
|
|
27
27
|
|
28
28
|
if defined?(::ActionView) && defined?(::ActionView::PartialRenderer)
|
29
29
|
ScoutApm::Agent.instance.logger.debug "Instrumenting ActionView::PartialRenderer"
|
30
|
-
ActionView::PartialRenderer.class_eval do
|
30
|
+
::ActionView::PartialRenderer.class_eval do
|
31
31
|
include ScoutApm::Tracer
|
32
|
-
instrument_method :render_partial,
|
32
|
+
instrument_method :render_partial,
|
33
|
+
:metric_name => 'View/#{@template.virtual_path rescue "Unknown Partial"}/Rendering',
|
34
|
+
:scope => true
|
35
|
+
|
36
|
+
instrument_method :collection_with_template,
|
37
|
+
:metric_name => 'View/#{@template.virtual_path rescue "Unknown Collection"}/Rendering',
|
38
|
+
:scope => true
|
33
39
|
end
|
34
|
-
end
|
35
40
|
|
41
|
+
ScoutApm::Agent.instance.logger.debug "Instrumenting ActionView::TemplateRenderer"
|
42
|
+
::ActionView::TemplateRenderer.class_eval do
|
43
|
+
include ScoutApm::Tracer
|
44
|
+
instrument_method :render_template,
|
45
|
+
:metric_name => 'View/#{args[0].virtual_path rescue "Unknown"}/Rendering',
|
46
|
+
:scope => true
|
47
|
+
end
|
48
|
+
end
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
@@ -42,13 +55,18 @@ module ScoutApm
|
|
42
55
|
scout_controller_action = "Controller/#{controller_path}/#{action_name}"
|
43
56
|
|
44
57
|
self.class.scout_apm_trace(scout_controller_action, :uri => request.fullpath, :ip => request.remote_ip) do
|
58
|
+
Thread::current[:scout_apm_prof] = nil
|
59
|
+
StackProf.start(mode: :wall, interval: ScoutApm::Agent.instance.config.value("stackprof_interval"))
|
60
|
+
|
45
61
|
begin
|
46
62
|
super
|
47
63
|
rescue Exception
|
48
|
-
ScoutApm::Agent.instance.store.track!("Errors/Request",1, :scope => nil)
|
64
|
+
ScoutApm::Agent.instance.store.track!("Errors/Request", 1, :scope => nil)
|
49
65
|
raise
|
50
66
|
ensure
|
51
67
|
Thread::current[:scout_apm_scope_name] = nil
|
68
|
+
StackProf.stop
|
69
|
+
Thread::current[:scout_apm_prof] = StackProf.results
|
52
70
|
end
|
53
71
|
end
|
54
72
|
end
|
@@ -56,9 +74,3 @@ module ScoutApm
|
|
56
74
|
end
|
57
75
|
end
|
58
76
|
|
59
|
-
|
60
|
-
# Rails 3/4
|
61
|
-
module ScoutApm
|
62
|
-
module Instruments
|
63
|
-
end
|
64
|
-
end
|
data/lib/scout_apm/reporter.rb
CHANGED
@@ -16,8 +16,8 @@ module ScoutApm
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# TODO: Parse & return a real response object, not the HTTP Response object
|
19
|
-
def report(payload)
|
20
|
-
post(uri, payload)
|
19
|
+
def report(payload, headers = {})
|
20
|
+
post(uri, payload, headers)
|
21
21
|
end
|
22
22
|
|
23
23
|
def uri
|
@@ -26,9 +26,26 @@ module ScoutApm
|
|
26
26
|
URI.parse("#{config.value('host')}/apps/checkin.scout?key=#{config.value('key')}&name=#{CGI.escape(Environment.instance.application_name)}")
|
27
27
|
when :app_server_load
|
28
28
|
URI.parse("#{config.value('host')}/apps/app_server_load.scout?key=#{config.value('key')}&name=#{CGI.escape(Environment.instance.application_name)}")
|
29
|
+
when :deploy_hook
|
30
|
+
URI.parse("#{config.value('host')}/apps/deploy.scout?key=#{config.value('key')}&name=#{CGI.escape(config.value('name'))}")
|
29
31
|
end.tap{|u| logger.debug("Posting to #{u.to_s}")}
|
30
32
|
end
|
31
33
|
|
34
|
+
def can_report?
|
35
|
+
case type
|
36
|
+
when :deploy_hook
|
37
|
+
%w(host key name).each do |k|
|
38
|
+
if config.value(k).nil?
|
39
|
+
logger.warn "/#{type} FAILED: missing required config value for #{k}"
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
return true
|
44
|
+
else
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
32
49
|
private
|
33
50
|
|
34
51
|
def post(uri, body, headers = Hash.new)
|
@@ -52,6 +69,8 @@ module ScoutApm
|
|
52
69
|
logger.debug "/#{type} OK"
|
53
70
|
when Net::HTTPBadRequest
|
54
71
|
logger.warn "/#{type} FAILED: The Account Key [#{config.value('key')}] is invalid."
|
72
|
+
when Net::HTTPUnprocessableEntity
|
73
|
+
logger.warn "/#{type} FAILED: #{response.body}"
|
55
74
|
else
|
56
75
|
logger.debug "/#{type} FAILED: #{response.inspect}"
|
57
76
|
end
|
@@ -0,0 +1,16 @@
|
|
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,36 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module ScoutApm
|
2
|
+
class SlowTransaction
|
3
|
+
BACKTRACE_THRESHOLD = 0.5 # the minimum threshold to record the backtrace for a metric.
|
4
|
+
BACKTRACE_LIMIT = 5 # Max length of callers to display
|
5
|
+
MAX_SIZE = 100 # Limits the size of the metric hash to prevent a metric explosion.
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
attr_reader :metric_name
|
8
|
+
attr_reader :total_call_time
|
9
|
+
attr_reader :metrics
|
10
|
+
attr_reader :meta
|
11
|
+
attr_reader :uri
|
12
|
+
attr_reader :context
|
13
|
+
attr_reader :time
|
14
|
+
attr_reader :prof
|
15
|
+
attr_reader :raw_prof
|
16
|
+
|
17
|
+
# Given a call stack, generates a filtered backtrace that:
|
18
|
+
# * Limits to the app/models, app/controllers, or app/views directories
|
19
|
+
# * Limits to 5 total callers
|
20
|
+
# * Makes the app folder the top-level folder used in trace info
|
21
|
+
def self.backtrace_parser(backtrace)
|
22
|
+
stack = []
|
23
|
+
backtrace.each do |c|
|
24
|
+
if m=c.match(/(\/app\/(controllers|models|views)\/.+)/)
|
25
|
+
stack << m[1]
|
26
|
+
break if stack.size == BACKTRACE_LIMIT
|
27
|
+
end
|
17
28
|
end
|
29
|
+
stack
|
18
30
|
end
|
19
|
-
stack
|
20
|
-
end
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
def initialize(uri, metric_name, total_call_time, metrics, context, time, raw_stackprof)
|
33
|
+
@uri = uri
|
34
|
+
@metric_name = metric_name
|
35
|
+
@total_call_time = total_call_time
|
36
|
+
@metrics = metrics
|
37
|
+
@context = context
|
38
|
+
@time = time
|
39
|
+
@prof = ScoutApm::StackprofTreeCollapser.new(raw_stackprof).call
|
40
|
+
@raw_prof = raw_stackprof # Send whole data up to server
|
41
|
+
end
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
# Used to remove metrics when the payload will be too large.
|
44
|
+
def clear_metrics!
|
45
|
+
@metrics = nil
|
46
|
+
self
|
47
|
+
end
|
35
48
|
end
|
36
49
|
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# require 'json'; p = JSON::parse(File.read("/Users/cschneid/example_stackprof.out")); p=p.with_indifferent_access; ScoutApm::StackprofTreeCollapser.new(p).call
|
2
|
+
# require 'json'; p = JSON::parse(File.read("/Users/cschneid/profile_appscontroller.json")); p=p.with_indifferent_access; ScoutApm::StackprofTreeCollapser.new(p).call
|
3
|
+
# require 'json'; p = JSON::parse(File.read("/Users/cschneid/profile_elasticsearch.json")); p=p.with_indifferent_access; ScoutApm::StackprofTreeCollapser.new(p).call
|
4
|
+
|
5
|
+
# in_app_nodes.map{|n| [n.samples_for_self_and_descendants, n.name, n.file, n.line]}.sort_by {|x| x[0] }
|
6
|
+
# in_app_nodes.map{|n| [n.total_samples, n.name] }.sort_by {|x| x[0] }
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
module ScoutApm
|
11
|
+
class StackprofTreeCollapser
|
12
|
+
attr_reader :raw_stackprof
|
13
|
+
attr_reader :nodes # the current set of nodes under consideration
|
14
|
+
|
15
|
+
def initialize(raw_stackprof)
|
16
|
+
@raw_stackprof = raw_stackprof
|
17
|
+
ScoutApm::Agent.instance.logger.info("StackProf - Samples: #{raw_stackprof[:samples]}, GC: #{raw_stackprof[:gc_samples]}, missed: #{raw_stackprof[:missed_samples]}, Interval: #{raw_stackprof[:interval]}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
build_tree
|
22
|
+
connect_children
|
23
|
+
total_samples_of_app_nodes
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_tree
|
29
|
+
@nodes = raw_stackprof[:frames].map do |(frame_id, frame_data)|
|
30
|
+
TreeNode.new(frame_id, # frame_id
|
31
|
+
frame_data[:name], # name
|
32
|
+
frame_data[:file], # file
|
33
|
+
frame_data[:line], # line
|
34
|
+
frame_data[:samples], # samples
|
35
|
+
frame_data[:total_samples], # total_samples
|
36
|
+
(frame_data[:edges] || {}), # children_edges [ { id => weight } ]
|
37
|
+
[], # children [ treenode, ... ]
|
38
|
+
[] # parents [ [treenode, int (weight) ], [...] ]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def connect_children
|
44
|
+
nodes.each do |node|
|
45
|
+
children = nodes.find_all { |n| node.children_edges.keys.include? n.frame_id }
|
46
|
+
|
47
|
+
node.children_edges.each do |(frame_id, weight)|
|
48
|
+
child = children.detect{ |c| c.frame_id == frame_id }
|
49
|
+
child.parents << [node, weight]
|
50
|
+
end
|
51
|
+
|
52
|
+
node.children = children
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def in_app_nodes
|
57
|
+
nodes.select {|n| n.app? }
|
58
|
+
end
|
59
|
+
|
60
|
+
def total_samples_of_app_nodes
|
61
|
+
in_app_nodes.reject{|n| n.calls_only_app_nodes? && !n.has_samples? }.
|
62
|
+
map{|n| { samples: n.total_samples,
|
63
|
+
name: n.name,
|
64
|
+
file: n.file,
|
65
|
+
line: n.line
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# @results will be [{name, samples, file, line}]
|
71
|
+
# def calculate_results
|
72
|
+
# @results = in_app_nodes.map do |node|
|
73
|
+
# desc = node.all_descendants
|
74
|
+
# total_samples = desc.map(&:samples).sum
|
75
|
+
# { desc_count: desc.length, name: node.name, file: node.file, line: node.line, samples: total_samples }
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
|
79
|
+
# def collapse_tree
|
80
|
+
# while true
|
81
|
+
# number_changed = collapse_tree_one_level
|
82
|
+
# break if number_changed == 0
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # For each leaf node, sees if it is internal to the monitored app. If not,
|
87
|
+
# # collapse that node to its parents, weighted by the edge counts
|
88
|
+
# # If that node was internal to the monitored app, leave it.
|
89
|
+
# # Returns 0 if nothing changed, a positive integer if things did change,
|
90
|
+
# # indicating how many leaves were collapsed
|
91
|
+
# def collapse_tree_one_level
|
92
|
+
# number_changed = 0
|
93
|
+
#
|
94
|
+
# puts "===========ITERATION==========="
|
95
|
+
# leaves.each do |leaf_node|
|
96
|
+
# next if leaf_node.app?
|
97
|
+
# puts "Collapsing - #{leaf_node.name}"
|
98
|
+
# # app parent: #{leaf_node.self_or_parents_in_app?.map {|x| x.name}}"
|
99
|
+
# number_changed += 1
|
100
|
+
# leaf_node.collapse_to_parent!
|
101
|
+
# @nodes = @nodes.reject { |n| n == leaf_node }
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# number_changed
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# # Returns the final result, an array of hashes
|
108
|
+
# def generate_output
|
109
|
+
# leaves.map{|x| { name: x.name, samples: x.samples, file: x.file, line: x.line } }
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# # A leaf node has no children.
|
113
|
+
# def leaves
|
114
|
+
# nodes.find_all { |n| n.children.empty? }
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
|
118
|
+
###########################################
|
119
|
+
# TreeNode class represents a single node.
|
120
|
+
###########################################
|
121
|
+
TreeNode = Struct.new(:frame_id, :name, :file, :line, :samples, :total_samples,
|
122
|
+
:children_edges, :children, :parents) do
|
123
|
+
def app?
|
124
|
+
@is_app ||= file =~ /^#{ScoutApm::Environment.instance.root}/
|
125
|
+
end
|
126
|
+
|
127
|
+
# My samples, and the weighted samples of all of my children
|
128
|
+
#def samples_for_self_and_descendants(seen=Set.new)
|
129
|
+
# viable_children = children.reject(&:app?)
|
130
|
+
# @samples_for_self_and_descendants ||= samples + viable_children.map{ |c_node|
|
131
|
+
# if seen.include? self
|
132
|
+
# puts "I've already seen #{self.name}, bailing"
|
133
|
+
# return samples # we've already been included, we're looping
|
134
|
+
# else
|
135
|
+
# seen << self
|
136
|
+
# c_node.samples_for_parent(self, seen.dup).tap { |val| puts "Child gave me #{val}" }
|
137
|
+
# end
|
138
|
+
# }.sum
|
139
|
+
#end
|
140
|
+
|
141
|
+
# For this parent of mine, how many of my samples do they get.
|
142
|
+
# is combo of "how many samples do I have, and what's the relative weight of this parent"
|
143
|
+
#def samples_for_parent(p_node, seen=Set.new)
|
144
|
+
# samples_for_self_and_descendants(seen) * relative_weight_of_parent(p_node)
|
145
|
+
#end
|
146
|
+
|
147
|
+
#def relative_weight_of_parent(p_node)
|
148
|
+
# total = parents.map{|(_, weight)| weight}.sum
|
149
|
+
# p_node_weight = parents.detect(0) {|(this_parent, _)| this_parent == p_node }[1]
|
150
|
+
# p_node_weight.to_f / total.to_f
|
151
|
+
#end
|
152
|
+
|
153
|
+
# Allocate this node's samples to its parents, in relation to the rate at
|
154
|
+
# which each parent called this method. Then clear the child from each of the parents
|
155
|
+
#def collapse_to_parent!
|
156
|
+
# total_weight = parents.map{ |(_, weight)| weight }.inject(0){ |sum, weight| sum + weight }
|
157
|
+
# parents.each do |(p_node, weight)|
|
158
|
+
# relative_weight = weight.to_f / total_weight.to_f
|
159
|
+
# p_node.samples += (samples * relative_weight)
|
160
|
+
# end
|
161
|
+
|
162
|
+
# parents.each {|(p_node, _)| p_node.delete_child!(self) }
|
163
|
+
#end
|
164
|
+
|
165
|
+
#def delete_child!(node)
|
166
|
+
# self.children = self.children.reject {|c| c == node }
|
167
|
+
#end
|
168
|
+
|
169
|
+
# Force object_id to be the equality mechanism, rather than struct's
|
170
|
+
# default which delegates to == on each value. That is wrong because
|
171
|
+
# we want to be able to dup a node in the tree construction process and
|
172
|
+
# not have those compare equal to each other.
|
173
|
+
def ==(other)
|
174
|
+
object_id == other.object_id
|
175
|
+
end
|
176
|
+
|
177
|
+
def inspect
|
178
|
+
"#{frame_id}: #{name} - ##{samples}\n" +
|
179
|
+
" Parents: #{parents.map{ |(p, w)| "#{p.name}: #{w}"}.join("\n ") }\n" +
|
180
|
+
" Children: #{children_edges.inspect} \n"
|
181
|
+
end
|
182
|
+
|
183
|
+
#def all_descendants(max_depth=100)
|
184
|
+
# descendants = [self]
|
185
|
+
# unchecked_edge = self.children.reject(&:app?)
|
186
|
+
# stop = false
|
187
|
+
|
188
|
+
# puts "----------------------------------------"
|
189
|
+
|
190
|
+
# while max_depth > 0 && !stop
|
191
|
+
# before_count = descendants.length
|
192
|
+
|
193
|
+
# descendants = (descendants + unchecked_edge).uniq
|
194
|
+
# unchecked_edge = unchecked_edge.map(&:children).flatten.uniq.reject(&:app?)
|
195
|
+
# puts "UncheckedEdge Children: #{unchecked_edge.length}"
|
196
|
+
|
197
|
+
# after_count = descendants.length
|
198
|
+
# stop = true if before_count == after_count
|
199
|
+
# max_depth = max_depth - 1
|
200
|
+
# end
|
201
|
+
|
202
|
+
# puts "#{name} - Found #{descendants.length} children after #{100 - max_depth} iterations"
|
203
|
+
|
204
|
+
# puts "----------------------------------------"
|
205
|
+
|
206
|
+
# descendants
|
207
|
+
#end
|
208
|
+
|
209
|
+
def calls_only_app_nodes?
|
210
|
+
children.all?(&:app?)
|
211
|
+
end
|
212
|
+
|
213
|
+
def has_samples?
|
214
|
+
samples > 0
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/scout_apm/store.rb
CHANGED
@@ -72,7 +72,7 @@ module ScoutApm
|
|
72
72
|
end
|
73
73
|
|
74
74
|
duration = Time.now - item.start_time
|
75
|
-
if last=stack.last
|
75
|
+
if last = stack.last
|
76
76
|
last.children_time += duration
|
77
77
|
end
|
78
78
|
|
@@ -90,8 +90,8 @@ module ScoutApm
|
|
90
90
|
|
91
91
|
# Uses controllers as the entry point for a transaction. Otherwise, stats are ignored.
|
92
92
|
if stack_empty and meta.metric_name.match(/\AController\//)
|
93
|
-
aggs=aggregate_calls(transaction_hash.dup,meta)
|
94
|
-
store_slow(options[:uri],transaction_hash.dup.merge(aggs),meta,stat)
|
93
|
+
aggs = aggregate_calls(transaction_hash.dup,meta)
|
94
|
+
store_slow(options[:uri], transaction_hash.dup.merge(aggs), meta, stat)
|
95
95
|
# deep duplicate
|
96
96
|
duplicate = aggs.dup
|
97
97
|
duplicate.each_pair do |k,v|
|
@@ -139,12 +139,20 @@ module ScoutApm
|
|
139
139
|
aggregates
|
140
140
|
end
|
141
141
|
|
142
|
+
SLOW_TRANSACTION_THRESHOLD = 2
|
143
|
+
|
142
144
|
# Stores slow transactions. This will be sent to the server.
|
143
|
-
def store_slow(uri,transaction_hash,parent_meta,parent_stat,options = {})
|
145
|
+
def store_slow(uri, transaction_hash, parent_meta, parent_stat, options = {})
|
144
146
|
@slow_transaction_lock.synchronize do
|
145
|
-
|
146
|
-
|
147
|
-
|
147
|
+
if parent_stat.total_call_time >= SLOW_TRANSACTION_THRESHOLD
|
148
|
+
slow_transaction = ScoutApm::SlowTransaction.new(uri,
|
149
|
+
parent_meta.metric_name,
|
150
|
+
parent_stat.total_call_time,
|
151
|
+
transaction_hash.dup,
|
152
|
+
ScoutApm::Context.current,
|
153
|
+
Thread::current[:scout_apm_trace_time],
|
154
|
+
Thread::current[:scout_apm_prof])
|
155
|
+
@slow_transactions.push(slow_transaction)
|
148
156
|
ScoutApm::Agent.instance.logger.debug "Slow transaction sample added. [URI: #{uri}] [Context: #{ScoutApm::Context.current.to_hash}] Array Size: #{@slow_transactions.size}"
|
149
157
|
end
|
150
158
|
end
|
data/lib/scout_apm/tracer.rb
CHANGED
@@ -54,13 +54,16 @@ module ScoutApm
|
|
54
54
|
elsif Thread::current[:scout_ignore_children]
|
55
55
|
return yield
|
56
56
|
end
|
57
|
+
|
57
58
|
if options.delete(:scope)
|
58
59
|
Thread::current[:scout_apm_sub_scope] = metric_name
|
59
60
|
end
|
61
|
+
|
60
62
|
if options[:ignore_children]
|
61
63
|
Thread::current[:scout_ignore_children] = true
|
62
64
|
end
|
63
65
|
stack_item = ScoutApm::Agent.instance.store.record(metric_name)
|
66
|
+
|
64
67
|
begin
|
65
68
|
yield
|
66
69
|
ensure
|
@@ -68,15 +71,21 @@ module ScoutApm
|
|
68
71
|
if options[:ignore_children]
|
69
72
|
Thread::current[:scout_ignore_children] = nil
|
70
73
|
end
|
74
|
+
|
71
75
|
ScoutApm::Agent.instance.store.stop_recording(stack_item,options)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
def instrument_method(method,options = {})
|
79
|
+
def instrument_method(method, options = {})
|
76
80
|
ScoutApm::Agent.instance.logger.info "Instrumenting #{method}"
|
77
81
|
metric_name = options[:metric_name] || default_metric_name(method)
|
78
82
|
return if !instrumentable?(method) or instrumented?(method,metric_name)
|
79
|
-
|
83
|
+
|
84
|
+
class_eval(instrumented_method_string(
|
85
|
+
method,
|
86
|
+
{:metric_name => metric_name, :scope => options[:scope] }),
|
87
|
+
__FILE__, __LINE__
|
88
|
+
)
|
80
89
|
|
81
90
|
alias_method _uninstrumented_method_name(method, metric_name), method
|
82
91
|
alias_method method, _instrumented_method_name(method, metric_name)
|
@@ -86,12 +95,15 @@ module ScoutApm
|
|
86
95
|
|
87
96
|
def instrumented_method_string(method, options)
|
88
97
|
klass = (self === Module) ? "self" : "self.class"
|
89
|
-
"def #{_instrumented_method_name(method, options[:metric_name])}(*args, &block)
|
98
|
+
method_str = "def #{_instrumented_method_name(method, options[:metric_name])}(*args, &block)
|
90
99
|
result = #{klass}.instrument(\"#{options[:metric_name]}\",{:scope => #{options[:scope] || false}}) do
|
91
100
|
#{_uninstrumented_method_name(method, options[:metric_name])}(*args, &block)
|
92
101
|
end
|
93
102
|
result
|
94
103
|
end"
|
104
|
+
|
105
|
+
ScoutApm::Agent.instance.logger.debug "Instrumented Method:\n#{method_str}"
|
106
|
+
method_str
|
95
107
|
end
|
96
108
|
|
97
109
|
# The method must exist to be instrumented.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# A fake implementation of stackprof, for systems that don't support it.
|
2
|
+
class StackProf
|
3
|
+
def self.start(*args)
|
4
|
+
@running = true
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.stop(*args)
|
8
|
+
@running = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def running?
|
12
|
+
!!@running
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*args)
|
16
|
+
start
|
17
|
+
yield
|
18
|
+
stop
|
19
|
+
results
|
20
|
+
end
|
21
|
+
|
22
|
+
def sample(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def results(*args)
|
26
|
+
{
|
27
|
+
:version => 0.0,
|
28
|
+
:mode => :wall,
|
29
|
+
:interval => 1000,
|
30
|
+
:samples => 0,
|
31
|
+
:gc_samples => 0,
|
32
|
+
:missed_samples => 0,
|
33
|
+
:frames => {},
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
data/lib/scout_apm/version.rb
CHANGED
data/scout_apm.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib","data"]
|
20
20
|
|
21
|
+
s.add_dependency 'stackprof'
|
22
|
+
|
21
23
|
s.add_development_dependency "minitest"
|
22
24
|
s.add_development_dependency "pry"
|
23
25
|
s.add_development_dependency "m"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.18.stackprof4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -11,6 +11,20 @@ bindir: bin
|
|
11
11
|
cert_chain: []
|
12
12
|
date: 2015-09-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: stackprof
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: minitest
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +89,10 @@ files:
|
|
75
89
|
- lib/scout_apm/capacity.rb
|
76
90
|
- lib/scout_apm/config.rb
|
77
91
|
- lib/scout_apm/context.rb
|
92
|
+
- lib/scout_apm/deploy_integrations/capistrano_2.cap
|
93
|
+
- lib/scout_apm/deploy_integrations/capistrano_2.rb
|
94
|
+
- lib/scout_apm/deploy_integrations/capistrano_3.cap
|
95
|
+
- lib/scout_apm/deploy_integrations/capistrano_3.rb
|
78
96
|
- lib/scout_apm/environment.rb
|
79
97
|
- lib/scout_apm/framework_integrations/rails_2.rb
|
80
98
|
- lib/scout_apm/framework_integrations/rails_3_or_4.rb
|
@@ -95,6 +113,7 @@ files:
|
|
95
113
|
- lib/scout_apm/metric_stats.rb
|
96
114
|
- lib/scout_apm/reporter.rb
|
97
115
|
- lib/scout_apm/serializers/app_server_load_serializer.rb
|
116
|
+
- lib/scout_apm/serializers/deploy_serializer.rb
|
98
117
|
- lib/scout_apm/serializers/directive_serializer.rb
|
99
118
|
- lib/scout_apm/serializers/payload_serializer.rb
|
100
119
|
- lib/scout_apm/server_integrations/null.rb
|
@@ -106,8 +125,10 @@ files:
|
|
106
125
|
- lib/scout_apm/server_integrations/webrick.rb
|
107
126
|
- lib/scout_apm/slow_transaction.rb
|
108
127
|
- lib/scout_apm/stack_item.rb
|
128
|
+
- lib/scout_apm/stackprof_tree_collapser.rb
|
109
129
|
- lib/scout_apm/store.rb
|
110
130
|
- lib/scout_apm/tracer.rb
|
131
|
+
- lib/scout_apm/utils/fake_stack_prof.rb
|
111
132
|
- lib/scout_apm/utils/installed_gems.rb
|
112
133
|
- lib/scout_apm/utils/null_logger.rb
|
113
134
|
- lib/scout_apm/utils/sql_sanitizer.rb
|
@@ -137,13 +158,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
158
|
version: '0'
|
138
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
160
|
requirements:
|
140
|
-
- - "
|
161
|
+
- - ">"
|
141
162
|
- !ruby/object:Gem::Version
|
142
|
-
version:
|
163
|
+
version: 1.3.1
|
143
164
|
requirements: []
|
144
165
|
rubyforge_project: scout_apm
|
145
166
|
rubygems_version: 2.2.2
|
146
167
|
signing_key:
|
147
168
|
specification_version: 4
|
148
169
|
summary: Ruby application performance monitoring
|
149
|
-
test_files:
|
170
|
+
test_files:
|
171
|
+
- test/data/config_test_1.yml
|
172
|
+
- test/test_helper.rb
|
173
|
+
- test/unit/config_test.rb
|
174
|
+
- test/unit/environment_test.rb
|
175
|
+
- test/unit/instruments/active_record_instruments_test.rb
|
176
|
+
- test/unit/sql_sanitizer_test.rb
|