scout_apm 1.5.5 → 1.6.0

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