scout_apm 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +6 -0
- data/lib/scout_apm.rb +1 -0
- data/lib/scout_apm/agent.rb +3 -0
- data/lib/scout_apm/agent/logging.rb +2 -2
- data/lib/scout_apm/agent/reporting.rb +1 -0
- data/lib/scout_apm/layer_converter.rb +38 -15
- data/lib/scout_apm/slow_request_policy.rb +83 -0
- data/lib/scout_apm/version.rb +1 -1
- data/test/test_helper.rb +19 -1
- data/test/unit/config_test.rb +2 -1
- data/test/unit/instruments/active_record_instruments_test.rb +0 -2
- data/test/unit/serializers/payload_serializer_test.rb +19 -16
- data/test/unit/sql_sanitizer_test.rb +5 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba5c475b02675dc137c1393e629891935ce8b340
|
4
|
+
data.tar.gz: a3b06a62571ee7b152351fee4893f2a9c395f067
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 046bf02d7bb03cec29d3922d5711353e61a873674ba82ee07bf9012ecd421fbfdcf872a8f88867c18604618b5173ca99352ec5ff66696a0c4a0c5b3bf77f0abc
|
7
|
+
data.tar.gz: dc675677e74bbb7138423b2268c8b3886338f95eed9db8d45f369f25c1149206f9cbf23d44f13e4bb2b5678b342757122c613c0db09710d6979bca1dc88f4226
|
data/CHANGELOG.markdown
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -97,6 +97,7 @@ require 'scout_apm/tracer'
|
|
97
97
|
require 'scout_apm/context'
|
98
98
|
require 'scout_apm/stackprof_tree_collapser'
|
99
99
|
require 'scout_apm/slow_transaction'
|
100
|
+
require 'scout_apm/slow_request_policy'
|
100
101
|
require 'scout_apm/capacity'
|
101
102
|
require 'scout_apm/attribute_arranger'
|
102
103
|
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -17,6 +17,7 @@ module ScoutApm
|
|
17
17
|
attr_accessor :log_file # path to the log file
|
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
|
+
attr_reader :slow_request_policy
|
20
21
|
|
21
22
|
# All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
|
22
23
|
def self.instance(options = {})
|
@@ -35,6 +36,8 @@ module ScoutApm
|
|
35
36
|
@layaway = ScoutApm::Layaway.new
|
36
37
|
@metric_lookup = Hash.new
|
37
38
|
|
39
|
+
@slow_request_policy = ScoutApm::SlowRequestPolicy.new
|
40
|
+
|
38
41
|
@capacity = ScoutApm::Capacity.new
|
39
42
|
@installed_instruments = []
|
40
43
|
end
|
@@ -8,12 +8,12 @@ module ScoutApm
|
|
8
8
|
|
9
9
|
def init_logger
|
10
10
|
begin
|
11
|
-
@log_file
|
11
|
+
@log_file ||= wants_stdout? ? STDOUT : "#{log_file_path}/scout_apm.log"
|
12
12
|
rescue => e
|
13
13
|
end
|
14
14
|
|
15
15
|
begin
|
16
|
-
@logger
|
16
|
+
@logger ||= Logger.new(@log_file)
|
17
17
|
@logger.level = log_level
|
18
18
|
apply_log_format
|
19
19
|
rescue Exception => e
|
@@ -88,22 +88,41 @@ module ScoutApm
|
|
88
88
|
|
89
89
|
# Take a TrackedRequest and turn it into either nil, or a SlowTransaction record
|
90
90
|
class LayerSlowTransactionConverter < LayerConverterBase
|
91
|
-
SLOW_REQUEST_TIME_THRESHOLD = 2 # seconds
|
92
|
-
|
93
91
|
def call
|
94
|
-
|
92
|
+
policy = ScoutApm::Agent.instance.slow_request_policy.capture_type(root_layer.total_call_time)
|
93
|
+
|
94
|
+
if policy == ScoutApm::SlowRequestPolicy::CAPTURE_NONE
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
95
98
|
|
96
99
|
scope = scope_layer
|
97
100
|
return nil unless scope
|
98
|
-
|
99
101
|
uri = request.annotations[:uri] || ""
|
102
|
+
|
103
|
+
metrics = case policy
|
104
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_SUMMARY
|
105
|
+
{}
|
106
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_DETAIL
|
107
|
+
create_metrics
|
108
|
+
end
|
109
|
+
|
110
|
+
stackprof = case policy
|
111
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_SUMMARY
|
112
|
+
[]
|
113
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_DETAIL
|
114
|
+
# Disable stackprof output for now
|
115
|
+
[] # request.stackprof
|
116
|
+
end
|
117
|
+
|
100
118
|
SlowTransaction.new(uri,
|
101
119
|
scope.legacy_metric_name,
|
102
120
|
root_layer.total_call_time,
|
103
|
-
|
121
|
+
metrics,
|
104
122
|
request.context,
|
105
123
|
root_layer.stop_time,
|
106
|
-
|
124
|
+
stackprof)
|
125
|
+
|
107
126
|
end
|
108
127
|
|
109
128
|
# Full metrics from this request. These get aggregated in Store for the
|
@@ -140,14 +159,15 @@ module ScoutApm
|
|
140
159
|
{:scope => scope_layer.legacy_metric_name}
|
141
160
|
end
|
142
161
|
|
143
|
-
|
144
162
|
# Specific Metric
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
163
|
+
if record_specific_metric?(layer.type)
|
164
|
+
meta_options.merge!(:desc => layer.desc) if layer.desc
|
165
|
+
meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
|
166
|
+
meta.extra.merge!(:backtrace => layer.backtrace) if layer.backtrace
|
167
|
+
metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
|
168
|
+
stat = metric_hash[meta]
|
169
|
+
stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
170
|
+
end
|
151
171
|
|
152
172
|
# Merged Metric (no specifics, just sum up by type)
|
153
173
|
meta = MetricMeta.new("#{layer.type}/all")
|
@@ -159,8 +179,11 @@ module ScoutApm
|
|
159
179
|
metric_hash
|
160
180
|
end
|
161
181
|
|
162
|
-
|
163
|
-
|
182
|
+
SKIP_SPECIFICS = ["Middleware"]
|
183
|
+
# For metrics that are known to be of sort duration (Middleware right now), we don't record specifics on each call to eliminate a metric explosion.
|
184
|
+
# There can be many Middlewares in an app.
|
185
|
+
def record_specific_metric?(name)
|
186
|
+
!SKIP_SPECIFICS.include?(name)
|
164
187
|
end
|
165
188
|
end
|
166
189
|
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Long running class that provides a yes/no answer to see if a transaction counts as a "slow transaction"
|
2
|
+
#
|
3
|
+
# Rules:
|
4
|
+
# - Runtime must be slower than a threshold
|
5
|
+
# - Log detailed metrics only for the first X
|
6
|
+
# - Stop logging anything after a maximum
|
7
|
+
|
8
|
+
module ScoutApm
|
9
|
+
class SlowRequestPolicy
|
10
|
+
CAPTURE_TYPES = [
|
11
|
+
CAPTURE_DETAIL = "capture_detail",
|
12
|
+
CAPTURE_SUMMARY = "capture_summary",
|
13
|
+
CAPTURE_NONE = "capture_none",
|
14
|
+
]
|
15
|
+
|
16
|
+
# It's not slow unless it's at least this slow
|
17
|
+
SLOW_REQUEST_TIME_THRESHOLD = 2.0 # seconds
|
18
|
+
|
19
|
+
# Stop recording detailed metrics after this count. Still record the fact
|
20
|
+
# a slow request happened though
|
21
|
+
MAX_DETAIL_PER_MINUTE = 10
|
22
|
+
|
23
|
+
# Stop recording anything after this number of slow transactions in a
|
24
|
+
# minute. Will also log a message once per minute that it is stopping
|
25
|
+
# recording.
|
26
|
+
MAX_PER_MINUTE = 500
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@minute_count = 0
|
30
|
+
@detailed_count = 0 # How many detailed slow transactions have we captured this minute?
|
31
|
+
@minute = Time.now.min
|
32
|
+
@clipped_recording = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def capture_type(time)
|
36
|
+
reset_counters
|
37
|
+
|
38
|
+
return CAPTURE_NONE unless slow_enough?(time)
|
39
|
+
return CAPTURE_NONE if clip_recording?
|
40
|
+
|
41
|
+
@minute_count += 1
|
42
|
+
|
43
|
+
if @detailed_count < MAX_DETAIL_PER_MINUTE
|
44
|
+
@detailed_count += 1
|
45
|
+
return CAPTURE_DETAIL
|
46
|
+
else
|
47
|
+
return CAPTURE_SUMMARY
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def reset_counters
|
55
|
+
t = Time.now.min
|
56
|
+
return if t == @minute
|
57
|
+
|
58
|
+
@minute_count = 0
|
59
|
+
@detailed_count = 0
|
60
|
+
@minute = t
|
61
|
+
@clipped_recording = false
|
62
|
+
end
|
63
|
+
|
64
|
+
def slow_enough?(time)
|
65
|
+
time > SLOW_REQUEST_TIME_THRESHOLD
|
66
|
+
end
|
67
|
+
|
68
|
+
# Breaker for rapid-fire excessive slow requests.
|
69
|
+
# If we trip this breaker continually, it will log once per minute that it is broken
|
70
|
+
def clip_recording?
|
71
|
+
if @minute_count > MAX_PER_MINUTE
|
72
|
+
if !@clipped_recording
|
73
|
+
ScoutApm::Agent.instance.logger.info("Skipping future slow requests this minute, reached limit of #{MAX_PER_MINUTE}")
|
74
|
+
end
|
75
|
+
|
76
|
+
@clipped_recording = true
|
77
|
+
true
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/scout_apm/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -5,6 +5,8 @@ require 'minitest/pride'
|
|
5
5
|
|
6
6
|
require 'pry'
|
7
7
|
|
8
|
+
require 'scout_apm'
|
9
|
+
|
8
10
|
Kernel.module_eval do
|
9
11
|
# Unset a constant without private access.
|
10
12
|
def self.const_unset(const)
|
@@ -12,5 +14,21 @@ Kernel.module_eval do
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
|
-
# require 'scout_apm'
|
16
17
|
|
18
|
+
# Helpers available to all tests
|
19
|
+
class Minitest::Test
|
20
|
+
def setup
|
21
|
+
reopen_logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_rack_env(env)
|
25
|
+
ENV['RACK_ENV'] = "production"
|
26
|
+
ScoutApm::Environment.instance.instance_variable_set("@env", nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reopen_logger
|
30
|
+
@log_contents = StringIO.new
|
31
|
+
@logger = Logger.new(@log_contents)
|
32
|
+
ScoutApm::Agent.instance.instance_variable_set("@logger", @logger)
|
33
|
+
end
|
34
|
+
end
|
data/test/unit/config_test.rb
CHANGED
@@ -13,17 +13,17 @@ require 'ostruct'
|
|
13
13
|
require 'json' # to deserialize what has been manually serialized by the production code
|
14
14
|
|
15
15
|
# stub the report_format value
|
16
|
-
class ScoutApm::Agent
|
17
|
-
module Config
|
18
|
-
def self.value(key)
|
19
|
-
'json'
|
20
|
-
end
|
21
|
-
end
|
16
|
+
# class ScoutApm::Agent
|
17
|
+
# module Config
|
18
|
+
# def self.value(key)
|
19
|
+
# 'json'
|
20
|
+
# end
|
21
|
+
# end
|
22
22
|
|
23
|
-
def self.instance
|
24
|
-
OpenStruct.new(:config => Config)
|
25
|
-
end
|
26
|
-
end
|
23
|
+
# def self.instance
|
24
|
+
# OpenStruct.new(:config => Config)
|
25
|
+
# end
|
26
|
+
# end
|
27
27
|
|
28
28
|
class PayloadSerializerTest < Minitest::Test
|
29
29
|
|
@@ -33,13 +33,14 @@ class PayloadSerializerTest < Minitest::Test
|
|
33
33
|
:unique_id => "unique_idz",
|
34
34
|
:agent_version => 123
|
35
35
|
}
|
36
|
-
payload = ScoutApm::Serializers::
|
36
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {})
|
37
37
|
|
38
38
|
# symbol keys turn to strings
|
39
39
|
formatted_metadata = {
|
40
40
|
"app_root" => "/srv/app/rootz",
|
41
41
|
"unique_id" => "unique_idz",
|
42
|
-
"agent_version" => 123
|
42
|
+
"agent_version" => 123,
|
43
|
+
"payload_version" => 2
|
43
44
|
}
|
44
45
|
assert_equal formatted_metadata, JSON.parse(payload)["metadata"]
|
45
46
|
end
|
@@ -73,7 +74,7 @@ class PayloadSerializerTest < Minitest::Test
|
|
73
74
|
stats.total_exclusive_time = 0.07813208899999999
|
74
75
|
}
|
75
76
|
}
|
76
|
-
payload = ScoutApm::Serializers::
|
77
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {})
|
77
78
|
formatted_metrics = [
|
78
79
|
{
|
79
80
|
"key" => {
|
@@ -145,7 +146,7 @@ class PayloadSerializerTest < Minitest::Test
|
|
145
146
|
context.add({"this" => "that"})
|
146
147
|
context.add_user({"hello" => "goodbye"})
|
147
148
|
slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), StackProf.new)
|
148
|
-
payload = ScoutApm::Serializers::
|
149
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, {}, [slow_t])
|
149
150
|
formatted_slow_transactions = [
|
150
151
|
{
|
151
152
|
"key" => {
|
@@ -200,12 +201,14 @@ class PayloadSerializerTest < Minitest::Test
|
|
200
201
|
def test_escapes_json_quotes
|
201
202
|
metadata = {
|
202
203
|
:quotie => "here are some \"quotes\"",
|
204
|
+
:payload_version => 2,
|
203
205
|
}
|
204
|
-
payload = ScoutApm::Serializers::
|
206
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {})
|
205
207
|
|
206
208
|
# symbol keys turn to strings
|
207
209
|
formatted_metadata = {
|
208
|
-
"quotie" => "here are some \"quotes\""
|
210
|
+
"quotie" => "here are some \"quotes\"",
|
211
|
+
"payload_version" => 2
|
209
212
|
}
|
210
213
|
assert_equal formatted_metadata, JSON.parse(payload)["metadata"]
|
211
214
|
end
|
@@ -5,6 +5,10 @@ require 'scout_apm/utils/sql_sanitizer'
|
|
5
5
|
module ScoutApm
|
6
6
|
module Utils
|
7
7
|
class SqlSanitizerTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
ScoutApm::Agent.instance.init_logger
|
10
|
+
end
|
11
|
+
|
8
12
|
# Too long, and we just bail out to prevent long running instrumentation
|
9
13
|
def test_long_sql
|
10
14
|
sql = " " * 1001
|
@@ -68,9 +72,7 @@ module ScoutApm
|
|
68
72
|
assert_equal false, sql.valid_encoding?
|
69
73
|
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
|
70
74
|
assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = 'a_c')|, ss.sql
|
71
|
-
|
72
|
-
assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = ?)|, ss.to_s
|
73
|
-
end
|
75
|
+
assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = ?)|, ss.to_s
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
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: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-01-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- lib/scout_apm/server_integrations/thin.rb
|
126
126
|
- lib/scout_apm/server_integrations/unicorn.rb
|
127
127
|
- lib/scout_apm/server_integrations/webrick.rb
|
128
|
+
- lib/scout_apm/slow_request_policy.rb
|
128
129
|
- lib/scout_apm/slow_transaction.rb
|
129
130
|
- lib/scout_apm/stack_item.rb
|
130
131
|
- lib/scout_apm/stackprof_tree_collapser.rb
|