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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 951fb125e1266317ef5b4e0ec486ec3b946be418
4
- data.tar.gz: 689544330b9f6a8f1b9f648257dc6eee972432b5
3
+ metadata.gz: ba5c475b02675dc137c1393e629891935ce8b340
4
+ data.tar.gz: a3b06a62571ee7b152351fee4893f2a9c395f067
5
5
  SHA512:
6
- metadata.gz: b99fc25a5c4af86e600a3f9b1a2a8cb53505e58d1b84468c075fc54a36ad361b36d3f0a4ca717339dfb30aeca344126487783771cd55ef397c79e28bc4a2a076
7
- data.tar.gz: d63b8be55f12a1435f1c4f74edad4062cc6d47b3e1e314f0e0cd91a0afa2d0947dbc8a7bae84258cf6df52c5392d65a8a4bf0d5ed50513ebac33a4561cc3eb71
6
+ metadata.gz: 046bf02d7bb03cec29d3922d5711353e61a873674ba82ee07bf9012ecd421fbfdcf872a8f88867c18604618b5173ca99352ec5ff66696a0c4a0c5b3bf77f0abc
7
+ data.tar.gz: dc675677e74bbb7138423b2268c8b3886338f95eed9db8d45f369f25c1149206f9cbf23d44f13e4bb2b5678b342757122c613c0db09710d6979bca1dc88f4226
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,9 @@
1
+ # 1.2.2
2
+
3
+ * Collapse middleware recordings to minimize payload size
4
+ * Limit slow transactions recorded in full detail each minute to prevent
5
+ overloading payload.
6
+
1
7
  # 1.2.1
2
8
 
3
9
  * Fix a small issue where the middleware that attempts to start the agent could
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
 
@@ -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 = wants_stdout? ? STDOUT : "#{log_file_path}/scout_apm.log"
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 = Logger.new(@log_file)
16
+ @logger ||= Logger.new(@log_file)
17
17
  @logger.level = log_level
18
18
  apply_log_format
19
19
  rescue Exception => e
@@ -44,6 +44,7 @@ module ScoutApm
44
44
  :agent_version => ScoutApm::VERSION,
45
45
  :agent_time => reporting_period.timestamp.to_s,
46
46
  :agent_pid => Process.pid,
47
+ :platform => "ruby",
47
48
  }
48
49
 
49
50
  log_deliver(metrics, slow_transactions, metadata)
@@ -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
- return nil unless should_capture_slow_request?
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
- create_metrics,
121
+ metrics,
104
122
  request.context,
105
123
  root_layer.stop_time,
106
- request.stackprof)
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
- meta_options.merge!(:desc => layer.desc) if layer.desc
146
- meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
147
- meta.extra.merge!(:backtrace => layer.backtrace) if layer.backtrace
148
- metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
149
- stat = metric_hash[meta]
150
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
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
- def should_capture_slow_request?
163
- root_layer.total_call_time > SLOW_REQUEST_TIME_THRESHOLD
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
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "1.2.1"
2
+ VERSION = "1.2.2"
3
3
  end
4
4
 
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
@@ -18,7 +18,8 @@ class ConfigTest < Minitest::Test
18
18
  end
19
19
 
20
20
  def test_loading_a_file
21
- ENV['RACK_ENV'] = "production"
21
+ set_rack_env("production")
22
+
22
23
  conf_file = File.expand_path("../../data/config_test_1.yml", __FILE__)
23
24
  conf = ScoutApm::Config.new(conf_file)
24
25
 
@@ -1,7 +1,5 @@
1
1
  require 'test_helper'
2
2
 
3
- require 'scout_apm/instruments/active_record_instruments'
4
-
5
3
  class ActiveRecordInstrumentsTest < Minitest::Test
6
4
  end
7
5
 
@@ -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::PayloadSerializer.serialize(metadata, {}, {})
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::PayloadSerializer.serialize({}, metrics, {})
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::PayloadSerializer.serialize({}, {}, [slow_t])
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::PayloadSerializer.serialize(metadata, {}, {})
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
- assert_nothing_raised do
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.1
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: 2015-12-23 00:00:00.000000000 Z
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