scout_apm 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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