scout_apm 1.5.5 → 1.6.0

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +8 -0
  3. data/lib/scout_apm.rb +3 -0
  4. data/lib/scout_apm/agent.rb +23 -25
  5. data/lib/scout_apm/agent/reporting.rb +8 -3
  6. data/lib/scout_apm/attribute_arranger.rb +4 -0
  7. data/lib/scout_apm/bucket_name_splitter.rb +3 -3
  8. data/lib/scout_apm/config.rb +4 -2
  9. data/lib/scout_apm/histogram.rb +20 -0
  10. data/lib/scout_apm/instruments/percentile_sampler.rb +37 -0
  11. data/lib/scout_apm/instruments/process/process_cpu.rb +12 -0
  12. data/lib/scout_apm/instruments/process/process_memory.rb +12 -0
  13. data/lib/scout_apm/layer_converters/converter_base.rb +6 -4
  14. data/lib/scout_apm/layer_converters/slow_job_converter.rb +21 -13
  15. data/lib/scout_apm/layer_converters/slow_request_converter.rb +28 -22
  16. data/lib/scout_apm/metric_meta.rb +5 -1
  17. data/lib/scout_apm/metric_set.rb +1 -1
  18. data/lib/scout_apm/reporter.rb +3 -1
  19. data/lib/scout_apm/request_histograms.rb +46 -0
  20. data/lib/scout_apm/scored_item_set.rb +79 -0
  21. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -0
  22. data/lib/scout_apm/slow_job_policy.rb +89 -19
  23. data/lib/scout_apm/slow_job_record.rb +20 -1
  24. data/lib/scout_apm/slow_request_policy.rb +80 -12
  25. data/lib/scout_apm/slow_transaction.rb +19 -2
  26. data/lib/scout_apm/store.rb +45 -15
  27. data/lib/scout_apm/tracked_request.rb +33 -10
  28. data/lib/scout_apm/version.rb +1 -1
  29. data/test/test_helper.rb +4 -3
  30. data/test/unit/layaway_test.rb +5 -8
  31. data/test/unit/scored_item_set_test.rb +65 -0
  32. data/test/unit/serializers/payload_serializer_test.rb +2 -1
  33. data/test/unit/slow_item_set_test.rb +2 -1
  34. data/test/unit/slow_request_policy_test.rb +42 -0
  35. metadata +9 -2
@@ -6,9 +6,13 @@ module ScoutApm
6
6
  # A hash of reporting periods. { StoreReportingPeriodTimestamp => StoreReportingPeriod }
7
7
  attr_reader :reporting_periods
8
8
 
9
+ # Used to pull metrics into each reporting period, as that reporting period is finished.
10
+ attr_reader :samplers
11
+
9
12
  def initialize
10
13
  @mutex = Mutex.new
11
14
  @reporting_periods = Hash.new { |h,k| h[k] = StoreReportingPeriod.new(k) }
15
+ @samplers = []
12
16
  end
13
17
 
14
18
  def current_timestamp
@@ -66,12 +70,32 @@ module ScoutApm
66
70
  @mutex.synchronize {
67
71
  reporting_periods.select { |time, rp| force || time.timestamp < current_timestamp.timestamp}.
68
72
  each { |time, rp|
73
+ collect_samplers(rp)
69
74
  layaway.add_reporting_period(time, rp)
70
75
  reporting_periods.delete(time)
71
76
  }
72
77
  }
73
78
  ScoutApm::Agent.instance.logger.debug("Finished writing to layaway")
74
79
  end
80
+
81
+ ######################################
82
+ # Sampler support
83
+ def add_sampler(sampler)
84
+ @samplers << sampler
85
+ end
86
+
87
+ def collect_samplers(rp)
88
+ @samplers.each do |sampler|
89
+ begin
90
+ metrics = sampler.metrics(rp.timestamp)
91
+ rp.absorb_metrics!(metrics)
92
+ rescue => e
93
+ ScoutApm::Agent.instance.logger.info "Error reading #{sampler.human_name} for period: #{rp}"
94
+ ScoutApm::Agent.instance.logger.debug e.message
95
+ ScoutApm::Agent.instance.logger.debug e.backtrace.join("\n")
96
+ end
97
+ end
98
+ end
75
99
  end
76
100
 
77
101
  # A timestamp, normalized to the beginning of a minute. Used as a hash key to
@@ -107,11 +131,12 @@ module ScoutApm
107
131
 
108
132
  # One period of Storage. Typically 1 minute
109
133
  class StoreReportingPeriod
110
- # A SlowItemSet to store slow transactions in
111
- attr_reader :slow_transactions
112
134
 
113
- # A SlowItemSet to store slow jobs in
114
- attr_reader :slow_jobs
135
+ # A ScoredItemSet holding the "best" traces for the period
136
+ attr_reader :request_traces
137
+
138
+ # A ScoredItemSet holding the "best" traces for the period
139
+ attr_reader :job_traces
115
140
 
116
141
  # A StoreReportingPeriodTimestamp representing the time that this
117
142
  # collection of metrics is for
@@ -122,19 +147,21 @@ module ScoutApm
122
147
  def initialize(timestamp)
123
148
  @timestamp = timestamp
124
149
 
125
- @slow_transactions = SlowItemSet.new
126
- @slow_jobs = SlowItemSet.new
150
+ @request_traces = ScoredItemSet.new
151
+ @job_traces = ScoredItemSet.new
127
152
 
128
153
  @metric_set = MetricSet.new
129
154
  @jobs = Hash.new
130
155
  end
131
156
 
132
157
  # Merges another StoreReportingPeriod into this one
133
- def merge(new_val)
158
+ def merge(other)
134
159
  self.
135
- merge_metrics!(new_val.metric_set).
136
- merge_slow_transactions!(new_val.slow_transactions).
137
- merge_jobs!(new_val.jobs)
160
+ merge_metrics!(other.metric_set).
161
+ merge_slow_transactions!(other.slow_transactions_payload).
162
+ merge_jobs!(other.jobs).
163
+ merge_slow_jobs!(other.slow_jobs_payload)
164
+ self
138
165
  end
139
166
 
140
167
  #################################
@@ -148,6 +175,7 @@ module ScoutApm
148
175
  end
149
176
 
150
177
  # For merging when you have another metric_set object
178
+ # Makes sure that you don't duplicate error count records
151
179
  def merge_metrics!(other_metric_set)
152
180
  metric_set.combine!(other_metric_set)
153
181
  self
@@ -155,14 +183,14 @@ module ScoutApm
155
183
 
156
184
  def merge_slow_transactions!(new_transactions)
157
185
  Array(new_transactions).each do |one_transaction|
158
- slow_transactions << one_transaction
186
+ request_traces << one_transaction
159
187
  end
160
188
 
161
189
  self
162
190
  end
163
191
 
164
192
  def merge_jobs!(jobs)
165
- jobs.each do |job|
193
+ Array(jobs).each do |job|
166
194
  if @jobs.has_key?(job)
167
195
  @jobs[job].combine!(job)
168
196
  else
@@ -175,8 +203,10 @@ module ScoutApm
175
203
 
176
204
  def merge_slow_jobs!(new_jobs)
177
205
  Array(new_jobs).each do |job|
178
- slow_jobs << job
206
+ job_traces << job
179
207
  end
208
+
209
+ self
180
210
  end
181
211
 
182
212
  #################################
@@ -187,7 +217,7 @@ module ScoutApm
187
217
  end
188
218
 
189
219
  def slow_transactions_payload
190
- slow_transactions.to_a
220
+ request_traces.to_a
191
221
  end
192
222
 
193
223
  def jobs
@@ -195,7 +225,7 @@ module ScoutApm
195
225
  end
196
226
 
197
227
  def slow_jobs_payload
198
- slow_jobs.to_a
228
+ job_traces.to_a
199
229
  end
200
230
 
201
231
  #################################
@@ -186,24 +186,47 @@ module ScoutApm
186
186
  def record!
187
187
  @recorded = true
188
188
 
189
+ # Update immediate and long-term histograms for both job and web requests
190
+ if unique_name != :unknown
191
+ ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
192
+ ScoutApm::Agent.instance.request_histograms_by_time[ScoutApm::Agent.instance.store.current_timestamp].
193
+ add(unique_name, root_layer.total_call_time)
194
+ end
195
+
189
196
  metrics = LayerConverters::MetricConverter.new(self).call
190
197
  ScoutApm::Agent.instance.store.track!(metrics)
191
198
 
192
- slow, slow_metrics = LayerConverters::SlowRequestConverter.new(self).call
193
- ScoutApm::Agent.instance.store.track_slow_transaction!(slow)
194
- ScoutApm::Agent.instance.store.track!(slow_metrics)
195
-
196
199
  error_metrics = LayerConverters::ErrorConverter.new(self).call
197
200
  ScoutApm::Agent.instance.store.track!(error_metrics)
198
201
 
199
- queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
200
- ScoutApm::Agent.instance.store.track!(queue_time_metrics)
202
+ if web?
203
+ # Don't #call this - that's the job of the ScoredItemSet later.
204
+ slow_converter = LayerConverters::SlowRequestConverter.new(self)
205
+ ScoutApm::Agent.instance.store.track_slow_transaction!(slow_converter)
206
+
207
+ queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
208
+ ScoutApm::Agent.instance.store.track!(queue_time_metrics)
209
+ end
210
+
211
+ if job?
212
+ job_metrics = LayerConverters::JobConverter.new(self).call
213
+ ScoutApm::Agent.instance.store.track_job!(job_metrics)
201
214
 
202
- job = LayerConverters::JobConverter.new(self).call
203
- ScoutApm::Agent.instance.store.track_job!(job)
215
+ job_converter = LayerConverters::SlowJobConverter.new(self)
216
+ ScoutApm::Agent.instance.store.track_slow_job!(job_converter)
217
+ end
218
+ end
204
219
 
205
- slow_job = LayerConverters::SlowJobConverter.new(self).call
206
- ScoutApm::Agent.instance.store.track_slow_job!(slow_job)
220
+ # Only call this after the request is complete
221
+ def unique_name
222
+ @unique_name ||= begin
223
+ scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
224
+ if scope_layer
225
+ scope_layer.legacy_metric_name
226
+ else
227
+ :unknown
228
+ end
229
+ end
207
230
  end
208
231
 
209
232
  # Have we already persisted this request?
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "1.5.5"
2
+ VERSION = "1.6.0"
3
3
  end
4
4
 
data/test/test_helper.rb CHANGED
@@ -1,11 +1,12 @@
1
+ # Load & Start simplecov before loading scout_apm
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+
1
5
  require 'minitest/autorun'
2
6
  require 'minitest/unit'
3
7
  require 'minitest/pride'
4
8
  require 'pry'
5
9
 
6
- # Load & Start simplecov before loading scout_apm
7
- require 'simplecov'
8
- SimpleCov.start
9
10
 
10
11
  require 'scout_apm'
11
12
 
@@ -18,15 +18,12 @@ class LayawayTest < Minitest::Test
18
18
 
19
19
  def test_merge_reporting_period
20
20
  File.open(DATA_FILE_PATH, 'w') { |file| file.write(Marshal.dump(NEW_FORMAT)) }
21
- ScoutApm::Agent.instance.start
22
-
23
- data = ScoutApm::Layaway.new
24
- t = ScoutApm::StoreReportingPeriodTimestamp.new
25
- data.add_reporting_period(TIMESTAMP,ScoutApm::StoreReportingPeriod.new(TIMESTAMP))
26
- assert_equal [TIMESTAMP], Marshal.load(File.read(DATA_FILE_PATH)).keys
27
- # TODO - add tests to verify metrics+slow transactions are merged
21
+ layaway = ScoutApm::Layaway.new
22
+ layaway.add_reporting_period(TIMESTAMP, ScoutApm::StoreReportingPeriod.new(TIMESTAMP))
23
+ unmarshalled = Marshal.load(File.read(DATA_FILE_PATH))
24
+ assert_equal [TIMESTAMP], unmarshalled.keys
28
25
  end
29
26
 
30
27
  TIMESTAMP = ScoutApm::StoreReportingPeriodTimestamp.new(Time.parse("2015-01-01"))
31
28
  NEW_FORMAT = {TIMESTAMP => ScoutApm::StoreReportingPeriod.new(TIMESTAMP)} # Format for 1.2+ agents
32
- end
29
+ end
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/scored_item_set'
4
+
5
+ class FakeScoredItem
6
+ def initialize(name, score)
7
+ @name = name
8
+ @score = score
9
+ end
10
+ def name; @name; end
11
+ def score; @score; end
12
+ def call; "called_#{@score}_#{@name}"; end
13
+ end
14
+
15
+ class ScoredItemSetTest < Minitest::Test
16
+ def test_empty_set_always_adds_item
17
+ set = ScoutApm::ScoredItemSet.new
18
+ set << FakeScoredItem.new("users/index", 10)
19
+
20
+ assert_equal set.to_a.first, "called_10_users/index"
21
+ assert_equal set.count, 1
22
+ end
23
+
24
+ def test_repeated_additions_chooses_most_expensive
25
+ set = ScoutApm::ScoredItemSet.new
26
+
27
+ [ FakeScoredItem.new("users/index", 10),
28
+ FakeScoredItem.new("users/index", 11),
29
+ FakeScoredItem.new("users/index", 12)
30
+ ].shuffle.each { |fsi| set << fsi }
31
+
32
+ assert_equal set.to_a.first, "called_12_users/index"
33
+ assert_equal set.count, 1
34
+ end
35
+
36
+ def test_multiple_items_occupy_different_buckets
37
+ set = ScoutApm::ScoredItemSet.new
38
+
39
+ [ FakeScoredItem.new("users/index", 10),
40
+ FakeScoredItem.new("users/index", 11),
41
+ FakeScoredItem.new("users/show", 12),
42
+ FakeScoredItem.new("users/show", 10)
43
+ ].shuffle.each { |fsi| set << fsi }
44
+
45
+ assert_equal set.count, 2
46
+ assert set.to_a.include?("called_11_users/index")
47
+ assert set.to_a.include?("called_12_users/show")
48
+ end
49
+
50
+ def test_evicts_at_capacity
51
+ set = ScoutApm::ScoredItemSet.new(3) # Force max_size to 3
52
+
53
+ [ FakeScoredItem.new("users/index", 10),
54
+ FakeScoredItem.new("users/show", 11),
55
+ FakeScoredItem.new("posts/index", 12),
56
+ FakeScoredItem.new("posts/move", 13)
57
+ ].shuffle.each { |fsi| set << fsi }
58
+
59
+ assert_equal set.count, 3
60
+ assert !set.to_a.include?("called_10_users/index"), "Did not Expect to see users/index in #{set.to_a.inspect}"
61
+ assert set.to_a.include?("called_11_users/show"), "Expected to see users/show in #{set.to_a.inspect}"
62
+ assert set.to_a.include?("called_12_posts/index"), "Expected to see posts/index in #{set.to_a.inspect}"
63
+ assert set.to_a.include?("called_13_posts/move"), "Expected to see posts/move in #{set.to_a.inspect}"
64
+ end
65
+ end
@@ -145,7 +145,7 @@ class PayloadSerializerTest < Minitest::Test
145
145
  context = ScoutApm::Context.new
146
146
  context.add({"this" => "that"})
147
147
  context.add_user({"hello" => "goodbye"})
148
- slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), [])
148
+ slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), [], 10)
149
149
  payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, {}, [slow_t], [], [])
150
150
  formatted_slow_transactions = [
151
151
  {
@@ -158,6 +158,7 @@ class PayloadSerializerTest < Minitest::Test
158
158
  "uri" => "http://example.com/blabla",
159
159
  "context" => {"this"=>"that", "user"=>{"hello"=>"goodbye"}},
160
160
  "prof" => [],
161
+ "score" => 10,
161
162
  "metrics" => [
162
163
  {
163
164
  "key" => {
@@ -88,6 +88,7 @@ class SlowItemSetTest < Minitest::Test
88
88
  {}, # metrics
89
89
  {}, # context
90
90
  Time.now, # end time
91
- []) # stackprof
91
+ [], # stackprof
92
+ 0)
92
93
  end
93
94
  end
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/slow_request_policy'
4
+ require 'scout_apm/layer'
5
+
6
+ class FakeRequest
7
+ def initialize(name)
8
+ @name = name
9
+ @root_layer = ScoutApm::Layer.new("Controller", name)
10
+ @root_layer.instance_variable_set("@stop_time", Time.now)
11
+ end
12
+ def unique_name; "Controller/foo/bar"; end
13
+ def root_layer; @root_layer; end
14
+ def set_duration(seconds)
15
+ @root_layer.instance_variable_set("@start_time", Time.now - seconds)
16
+ end
17
+ end
18
+
19
+ class SlowRequestPolicyTest < Minitest::Test
20
+ def test_stored_records_current_time
21
+ test_start = Time.now
22
+ policy = ScoutApm::SlowRequestPolicy.new
23
+ request = FakeRequest.new("users/index")
24
+
25
+ policy.stored!(request)
26
+ assert policy.last_seen[request.unique_name] > test_start
27
+ end
28
+
29
+ def test_score
30
+ policy = ScoutApm::SlowRequestPolicy.new
31
+ request = FakeRequest.new("users/index")
32
+
33
+ request.set_duration(10) # 10 seconds
34
+ policy.last_seen[request.unique_name] = Time.now - 120 # 2 minutes since last seen
35
+ agent.request_histograms.add(request.unique_name, 1)
36
+
37
+ # Actual value I have in console is 1.599
38
+ assert policy.score(request) > 1.5
39
+ assert policy.score(request) < 2.0
40
+
41
+ end
42
+ 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.5.5
4
+ version: 1.6.0
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: 2016-05-24 00:00:00.000000000 Z
12
+ date: 2016-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -117,6 +117,7 @@ files:
117
117
  - lib/scout_apm/instruments/mongoid.rb
118
118
  - lib/scout_apm/instruments/moped.rb
119
119
  - lib/scout_apm/instruments/net_http.rb
120
+ - lib/scout_apm/instruments/percentile_sampler.rb
120
121
  - lib/scout_apm/instruments/process/process_cpu.rb
121
122
  - lib/scout_apm/instruments/process/process_memory.rb
122
123
  - lib/scout_apm/instruments/rails_router.rb
@@ -142,7 +143,9 @@ files:
142
143
  - lib/scout_apm/platform_integrations/heroku.rb
143
144
  - lib/scout_apm/platform_integrations/server.rb
144
145
  - lib/scout_apm/reporter.rb
146
+ - lib/scout_apm/request_histograms.rb
145
147
  - lib/scout_apm/request_manager.rb
148
+ - lib/scout_apm/scored_item_set.rb
146
149
  - lib/scout_apm/serializers/app_server_load_serializer.rb
147
150
  - lib/scout_apm/serializers/deploy_serializer.rb
148
151
  - lib/scout_apm/serializers/directive_serializer.rb
@@ -189,9 +192,11 @@ files:
189
192
  - test/unit/instruments/active_record_instruments_test.rb
190
193
  - test/unit/layaway_test.rb
191
194
  - test/unit/metric_set_test.rb
195
+ - test/unit/scored_item_set_test.rb
192
196
  - test/unit/serializers/payload_serializer_test.rb
193
197
  - test/unit/slow_item_set_test.rb
194
198
  - test/unit/slow_job_policy_test.rb
199
+ - test/unit/slow_request_policy_test.rb
195
200
  - test/unit/sql_sanitizer_test.rb
196
201
  - test/unit/utils/active_record_metric_name_test.rb
197
202
  homepage: https://github.com/scoutapp/scout_apm_ruby
@@ -228,9 +233,11 @@ test_files:
228
233
  - test/unit/instruments/active_record_instruments_test.rb
229
234
  - test/unit/layaway_test.rb
230
235
  - test/unit/metric_set_test.rb
236
+ - test/unit/scored_item_set_test.rb
231
237
  - test/unit/serializers/payload_serializer_test.rb
232
238
  - test/unit/slow_item_set_test.rb
233
239
  - test/unit/slow_job_policy_test.rb
240
+ - test/unit/slow_request_policy_test.rb
234
241
  - test/unit/sql_sanitizer_test.rb
235
242
  - test/unit/utils/active_record_metric_name_test.rb
236
243
  has_rdoc: