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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +8 -0
- data/lib/scout_apm.rb +3 -0
- data/lib/scout_apm/agent.rb +23 -25
- data/lib/scout_apm/agent/reporting.rb +8 -3
- data/lib/scout_apm/attribute_arranger.rb +4 -0
- data/lib/scout_apm/bucket_name_splitter.rb +3 -3
- data/lib/scout_apm/config.rb +4 -2
- data/lib/scout_apm/histogram.rb +20 -0
- data/lib/scout_apm/instruments/percentile_sampler.rb +37 -0
- data/lib/scout_apm/instruments/process/process_cpu.rb +12 -0
- data/lib/scout_apm/instruments/process/process_memory.rb +12 -0
- data/lib/scout_apm/layer_converters/converter_base.rb +6 -4
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +21 -13
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +28 -22
- data/lib/scout_apm/metric_meta.rb +5 -1
- data/lib/scout_apm/metric_set.rb +1 -1
- data/lib/scout_apm/reporter.rb +3 -1
- data/lib/scout_apm/request_histograms.rb +46 -0
- data/lib/scout_apm/scored_item_set.rb +79 -0
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -0
- data/lib/scout_apm/slow_job_policy.rb +89 -19
- data/lib/scout_apm/slow_job_record.rb +20 -1
- data/lib/scout_apm/slow_request_policy.rb +80 -12
- data/lib/scout_apm/slow_transaction.rb +19 -2
- data/lib/scout_apm/store.rb +45 -15
- data/lib/scout_apm/tracked_request.rb +33 -10
- data/lib/scout_apm/version.rb +1 -1
- data/test/test_helper.rb +4 -3
- data/test/unit/layaway_test.rb +5 -8
- data/test/unit/scored_item_set_test.rb +65 -0
- data/test/unit/serializers/payload_serializer_test.rb +2 -1
- data/test/unit/slow_item_set_test.rb +2 -1
- data/test/unit/slow_request_policy_test.rb +42 -0
- metadata +9 -2
data/lib/scout_apm/store.rb
CHANGED
@@ -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
|
114
|
-
attr_reader :
|
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
|
-
@
|
126
|
-
@
|
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(
|
158
|
+
def merge(other)
|
134
159
|
self.
|
135
|
-
merge_metrics!(
|
136
|
-
merge_slow_transactions!(
|
137
|
-
merge_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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
215
|
+
job_converter = LayerConverters::SlowJobConverter.new(self)
|
216
|
+
ScoutApm::Agent.instance.store.track_slow_job!(job_converter)
|
217
|
+
end
|
218
|
+
end
|
204
219
|
|
205
|
-
|
206
|
-
|
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?
|
data/lib/scout_apm/version.rb
CHANGED
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
|
|
data/test/unit/layaway_test.rb
CHANGED
@@ -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::
|
22
|
-
|
23
|
-
|
24
|
-
|
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" => {
|
@@ -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.
|
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-
|
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:
|