scout_apm 2.4.24 → 2.5.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 +5 -0
- data/README.markdown +7 -4
- data/lib/scout_apm.rb +3 -0
- data/lib/scout_apm/agent_context.rb +1 -1
- data/lib/scout_apm/config.rb +5 -1
- data/lib/scout_apm/detailed_trace.rb +216 -0
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/layer.rb +2 -0
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +9 -1
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -1
- data/lib/scout_apm/layer_converters/trace_converter.rb +180 -0
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/serializers/payload_serializer.rb +2 -2
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +2 -1
- data/lib/scout_apm/slow_job_record.rb +5 -1
- data/lib/scout_apm/slow_transaction.rb +3 -1
- data/lib/scout_apm/store.rb +0 -1
- data/lib/scout_apm/tracked_request.rb +11 -0
- data/lib/scout_apm/utils/unique_id.rb +27 -0
- data/lib/scout_apm/version.rb +1 -1
- data/test/unit/serializers/payload_serializer_test.rb +3 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2003c0ac2f53ba79444a99beff51200791cadd27
|
4
|
+
data.tar.gz: aaa6bac945f0c541a39e6326f79a1bbd4959a663
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 516804f4427ca84b89ae752964d6e38653c29698c1f833b4facec0ed784dde8e5212c11fdca62fc6f837a94a5cebabcd68130331f89816374881901d5382f750
|
7
|
+
data.tar.gz: 07f51e83d3aa6b79d883113b430fd20a0814dcae613285be6f3547a588407f7aa5eb02d9b5972bf01b4c4ca8aaf3a9f2191ce0025afa016780d7a05caf65310c
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# 2.5.0
|
2
|
+
|
3
|
+
* Added timeline traces and an associated `timeline_traces: true` config option.
|
4
|
+
* Increased timeline traces span limit to 2,500 from 500.
|
5
|
+
|
1
6
|
# 2.4.24
|
2
7
|
|
3
8
|
* Fix for prepending view instruments in the case of templates that lack a `virtual_path` (#257).
|
data/README.markdown
CHANGED
@@ -12,8 +12,8 @@ The Scout agent is engineered to do some wonderful things:
|
|
12
12
|
|
13
13
|
* A unique focus on identifying those hard-to-investigate outliers like memory bloat, N+1s, and user-specific problems. [See an example workflow](http://scoutapp.com/newrelic-alternative).
|
14
14
|
* [Low-overhead](http://blog.scoutapp.com/articles/2016/02/07/overhead-benchmarks-new-relic-vs-scout)
|
15
|
-
* View your performance metrics during development with [DevTrace](https://
|
16
|
-
* Production-Safe profiling of custom code via [ScoutProf](https://
|
15
|
+
* View your performance metrics during development with [DevTrace](https://docs.scoutapm.com/#devtrace) and in production via [server_timing](https://github.com/scoutapp/ruby_server_timing).
|
16
|
+
* Production-Safe profiling of custom code via [ScoutProf](https://docs.scoutapm.com/#scoutprof) (BETA).
|
17
17
|
|
18
18
|
## Getting Started
|
19
19
|
|
@@ -25,7 +25,7 @@ Update your Gemfile
|
|
25
25
|
|
26
26
|
bundle install
|
27
27
|
|
28
|
-
Signup for a [Scout](https://
|
28
|
+
Signup for a [Scout](https://scoutapm.com) account and put the provided
|
29
29
|
config file at `RAILS_ROOT/config/scout_apm.yml`.
|
30
30
|
|
31
31
|
Your config file should look like:
|
@@ -35,6 +35,9 @@ Your config file should look like:
|
|
35
35
|
key: YOUR_APPLICATION_KEY
|
36
36
|
monitor: true
|
37
37
|
|
38
|
+
test:
|
39
|
+
monitor: false
|
40
|
+
|
38
41
|
production:
|
39
42
|
<<: *defaults
|
40
43
|
|
@@ -64,7 +67,7 @@ SCOUT_DEV_TRACE=true rails server
|
|
64
67
|
## Docs
|
65
68
|
|
66
69
|
For the complete list of supported frameworks, Rubies, configuration options
|
67
|
-
and more, see our [help site](https://
|
70
|
+
and more, see our [help site](https://docs.scoutapm.com/).
|
68
71
|
|
69
72
|
## Help
|
70
73
|
|
data/lib/scout_apm.rb
CHANGED
@@ -15,6 +15,7 @@ require 'socket'
|
|
15
15
|
require 'thread'
|
16
16
|
require 'time'
|
17
17
|
require 'yaml'
|
18
|
+
require 'securerandom'
|
18
19
|
|
19
20
|
#####################################
|
20
21
|
# Gem Requires
|
@@ -44,6 +45,7 @@ require 'scout_apm/layer_converters/database_converter'
|
|
44
45
|
require 'scout_apm/layer_converters/slow_request_converter'
|
45
46
|
require 'scout_apm/layer_converters/request_queue_time_converter'
|
46
47
|
require 'scout_apm/layer_converters/allocation_metric_converter'
|
48
|
+
require 'scout_apm/layer_converters/trace_converter'
|
47
49
|
require 'scout_apm/layer_converters/histograms'
|
48
50
|
require 'scout_apm/layer_converters/find_layer_by_type'
|
49
51
|
|
@@ -136,6 +138,7 @@ require 'scout_apm/metric_stats'
|
|
136
138
|
require 'scout_apm/db_query_metric_stats'
|
137
139
|
require 'scout_apm/slow_transaction'
|
138
140
|
require 'scout_apm/slow_job_record'
|
141
|
+
require 'scout_apm/detailed_trace'
|
139
142
|
require 'scout_apm/scored_item_set'
|
140
143
|
require 'scout_apm/slow_request_policy'
|
141
144
|
require 'scout_apm/slow_job_policy'
|
@@ -205,7 +205,7 @@ module ScoutApm
|
|
205
205
|
if !@config.any_keys_found?
|
206
206
|
logger.info("No configuration file loaded, and no configuration found in ENV. " +
|
207
207
|
"For assistance configuring Scout, visit " +
|
208
|
-
"https://
|
208
|
+
"https://docs.scoutapm.com/#ruby-configuration-options")
|
209
209
|
end
|
210
210
|
end
|
211
211
|
end
|
data/lib/scout_apm/config.rb
CHANGED
@@ -6,7 +6,7 @@ require 'scout_apm/environment'
|
|
6
6
|
# Valid Config Options:
|
7
7
|
#
|
8
8
|
# This list is complete, but some are old and unused, or for developers of
|
9
|
-
# scout_apm itself. See the documentation at https://
|
9
|
+
# scout_apm itself. See the documentation at https://docs.scoutapm.com for
|
10
10
|
# customer-focused documentation.
|
11
11
|
#
|
12
12
|
# application_root - override the detected directory of the application
|
@@ -32,6 +32,7 @@ require 'scout_apm/environment'
|
|
32
32
|
# remote_agent_host - Internal: What host to bind to, and also send messages to for remote. Default: 127.0.0.1.
|
33
33
|
# remote_agent_port - What port to bind the remote webserver to
|
34
34
|
# start_resque_server_instrument - Used in special situations with certain Resque installs
|
35
|
+
# timeline_traces - true/false to enable sending of of the timeline trace format.
|
35
36
|
#
|
36
37
|
# Any of these config settings can be set with an environment variable prefixed
|
37
38
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
@@ -74,6 +75,7 @@ module ScoutApm
|
|
74
75
|
'start_resque_server_instrument',
|
75
76
|
'uri_reporting',
|
76
77
|
'instrument_http_url_length',
|
78
|
+
'timeline_traces'
|
77
79
|
]
|
78
80
|
|
79
81
|
################################################################################
|
@@ -166,6 +168,7 @@ module ScoutApm
|
|
166
168
|
'database_metric_report_limit' => IntegerCoercion.new,
|
167
169
|
'instrument_http_url_length' => IntegerCoercion.new,
|
168
170
|
'start_resque_server_instrument' => BooleanCoercion.new,
|
171
|
+
'timeline_traces' => BooleanCoercion.new
|
169
172
|
}
|
170
173
|
|
171
174
|
|
@@ -273,6 +276,7 @@ module ScoutApm
|
|
273
276
|
'instrument_http_url_length' => 300,
|
274
277
|
'start_resque_server_instrument' => true, # still only starts if Resque is detected
|
275
278
|
'collect_remote_ip' => true,
|
279
|
+
'timeline_traces' => true
|
276
280
|
}.freeze
|
277
281
|
|
278
282
|
def value(key)
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# DetailedTrace contains all details about a certain transaction, spans with
|
2
|
+
# start & stop times, tags, etc.
|
3
|
+
|
4
|
+
# {
|
5
|
+
# "version": 1,
|
6
|
+
# "identity": {
|
7
|
+
# "transaction_id": "req-....",
|
8
|
+
# "revision": "abcdef",
|
9
|
+
# "start_instant": "01-01-01T00:00:00.0000Z",
|
10
|
+
# "stop_instant": "01-01-01T00:00:01.0000Z",
|
11
|
+
# "type": "Web",
|
12
|
+
# "naming": {
|
13
|
+
# "path": "/users",
|
14
|
+
# "code": "UsersController#index",
|
15
|
+
# },
|
16
|
+
# "score": {
|
17
|
+
# "total": 10.5,
|
18
|
+
# "percentile": 4.5,
|
19
|
+
# "age": 2.0,
|
20
|
+
# "memory_delta": 3,
|
21
|
+
# "allocations": 1
|
22
|
+
# }
|
23
|
+
# },
|
24
|
+
#
|
25
|
+
# "tags": {
|
26
|
+
# "allocations": 1000
|
27
|
+
# },
|
28
|
+
#
|
29
|
+
# "spans": [
|
30
|
+
# ...
|
31
|
+
# ]
|
32
|
+
|
33
|
+
class DetailedTrace
|
34
|
+
attr_reader :spans
|
35
|
+
attr_reader :tags
|
36
|
+
|
37
|
+
attr_reader :transaction_id
|
38
|
+
attr_reader :revision
|
39
|
+
attr_reader :start_instant
|
40
|
+
attr_reader :stop_instant
|
41
|
+
attr_reader :duration
|
42
|
+
attr_reader :type # "Web" or "Job"
|
43
|
+
attr_reader :host
|
44
|
+
|
45
|
+
attr_reader :path # /users/1
|
46
|
+
attr_reader :code # UsersController#show or similar
|
47
|
+
|
48
|
+
attr_reader :total_score
|
49
|
+
attr_reader :percentile_score
|
50
|
+
attr_reader :age_score
|
51
|
+
attr_reader :memory_delta_score
|
52
|
+
attr_reader :memory_allocations_score
|
53
|
+
|
54
|
+
VERSION = 1
|
55
|
+
|
56
|
+
def initialize(transaction_id, revision, host, start_instant, stop_instant, type, path, code, spans, tags)
|
57
|
+
@spans = spans
|
58
|
+
@tags = DetailedTraceTags(tags)
|
59
|
+
|
60
|
+
@transaction_id = transaction_id
|
61
|
+
@revision = revision
|
62
|
+
@host = host
|
63
|
+
@start_instant = start_instant
|
64
|
+
@stop_instant = stop_instant
|
65
|
+
@type = type
|
66
|
+
|
67
|
+
@path = path
|
68
|
+
@code = code
|
69
|
+
|
70
|
+
@total_score = 0
|
71
|
+
@percentile_score = 0
|
72
|
+
@age_score = 0
|
73
|
+
@memory_delta_score = 0
|
74
|
+
@memory_allocations_score = 0
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def as_json(*)
|
79
|
+
{
|
80
|
+
:version => VERSION,
|
81
|
+
:identity => {
|
82
|
+
:transaction_id => transaction_id,
|
83
|
+
:revision => revision,
|
84
|
+
:host => host,
|
85
|
+
:start_instant => start_instant.iso8601(6),
|
86
|
+
:stop_instant => stop_instant.iso8601(6),
|
87
|
+
:type => type,
|
88
|
+
:naming => {
|
89
|
+
:path => path,
|
90
|
+
:code => code,
|
91
|
+
},
|
92
|
+
:score => {
|
93
|
+
:total => total_score,
|
94
|
+
:percentile => percentile_score,
|
95
|
+
:age => age_score,
|
96
|
+
:memory_delta => memory_delta_score,
|
97
|
+
:allocations => memory_allocations_score,
|
98
|
+
}
|
99
|
+
},
|
100
|
+
:tags => tags.as_json,
|
101
|
+
:spans => spans.as_json,
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
########################
|
106
|
+
# Scorable interface
|
107
|
+
#
|
108
|
+
# Needed so we can merge ScoredItemSet instances
|
109
|
+
def call
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def name
|
114
|
+
code
|
115
|
+
end
|
116
|
+
|
117
|
+
def score
|
118
|
+
@total_score
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
##########
|
124
|
+
# SPAN #
|
125
|
+
##########
|
126
|
+
|
127
|
+
#
|
128
|
+
# {
|
129
|
+
# "type": "Standard",
|
130
|
+
# "identity": {
|
131
|
+
# "id": "....",
|
132
|
+
# "parent_id": "....",
|
133
|
+
# "start_time": "01-01-01T00:00:00.0000Z",
|
134
|
+
# "stop_time": "01-01-01T00:00:00.0001Z",
|
135
|
+
# "operation": "SQL/User/find"
|
136
|
+
# },
|
137
|
+
# "tags": {
|
138
|
+
# "allocations": 1000,
|
139
|
+
# "db.statement": "SELECT * FROM users where id = 1",
|
140
|
+
# "db.rows": 1,
|
141
|
+
# "backtrace": [ {
|
142
|
+
# "file": "app/controllers/users_controller.rb",
|
143
|
+
# "line": 10,
|
144
|
+
# "function": "index"
|
145
|
+
# } ]
|
146
|
+
# }
|
147
|
+
class DetailedTraceSpan
|
148
|
+
attr_reader :tags
|
149
|
+
|
150
|
+
attr_reader :span_type
|
151
|
+
attr_reader :span_id, :parent_id
|
152
|
+
attr_reader :start_instant, :stop_instant
|
153
|
+
|
154
|
+
# What is the "name" of this span.
|
155
|
+
#
|
156
|
+
# Examples:
|
157
|
+
# SQL/User/find
|
158
|
+
# Controller/Users/index
|
159
|
+
# HTTP/GET/example.com
|
160
|
+
attr_reader :operation
|
161
|
+
|
162
|
+
def initialize(span_id, parent_id, start_instant, stop_instant, operation, tags)
|
163
|
+
# This will be dynamic when we implement limited spans
|
164
|
+
@span_type = "Standard"
|
165
|
+
|
166
|
+
@span_id = span_id
|
167
|
+
@parent_id = parent_id
|
168
|
+
|
169
|
+
@start_instant = start_instant
|
170
|
+
@stop_instant = stop_instant
|
171
|
+
@operation = operation
|
172
|
+
@tags = DetailedTraceTags(tags)
|
173
|
+
end
|
174
|
+
|
175
|
+
def as_json(*)
|
176
|
+
{
|
177
|
+
:type => @span_type,
|
178
|
+
:identity => {
|
179
|
+
:id => span_id,
|
180
|
+
:parent_id => parent_id,
|
181
|
+
:start_instant => start_instant.iso8601(6),
|
182
|
+
:stop_instant => stop_instant.iso8601(6),
|
183
|
+
:operation => operation,
|
184
|
+
},
|
185
|
+
:tags => @tags.as_json,
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
#############
|
192
|
+
# content #
|
193
|
+
#############
|
194
|
+
|
195
|
+
# Tags for either a request, or a span
|
196
|
+
class DetailedTraceTags
|
197
|
+
attr_reader :tags
|
198
|
+
|
199
|
+
def initialize(hash)
|
200
|
+
@tags = hash
|
201
|
+
end
|
202
|
+
|
203
|
+
def as_json(*)
|
204
|
+
@tags.as_json
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Converter function to turn an input into a DetailedTraceTags object
|
209
|
+
def DetailedTraceTags(arg)
|
210
|
+
if DetailedTraceTags === arg
|
211
|
+
arg
|
212
|
+
elsif Hash === arg
|
213
|
+
DetailedTraceTags.new(arg)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
data/lib/scout_apm/fake_store.rb
CHANGED
data/lib/scout_apm/layer.rb
CHANGED
@@ -46,6 +46,8 @@ module ScoutApm
|
|
46
46
|
# If no annotations are ever set, this will return nil
|
47
47
|
attr_reader :annotations
|
48
48
|
|
49
|
+
attr_reader :allocations_start, :allocations_stop
|
50
|
+
|
49
51
|
BACKTRACE_CALLER_LIMIT = 50 # maximum number of lines to send thru for backtrace analysis
|
50
52
|
|
51
53
|
def initialize(type, name, start_time = Time.now)
|
@@ -15,6 +15,7 @@ module ScoutApm
|
|
15
15
|
# Let the store know we're here, and if it wants our data, it will call
|
16
16
|
# back into #call
|
17
17
|
@store.track_slow_job!(self)
|
18
|
+
|
18
19
|
nil # not returning anything in the layer results ... not used
|
19
20
|
end
|
20
21
|
|
@@ -54,7 +55,8 @@ module ScoutApm
|
|
54
55
|
mem_delta,
|
55
56
|
job_layer.total_allocations,
|
56
57
|
score,
|
57
|
-
limited
|
58
|
+
limited?,
|
59
|
+
span_trace
|
58
60
|
)
|
59
61
|
end
|
60
62
|
|
@@ -89,6 +91,12 @@ module ScoutApm
|
|
89
91
|
def skip_layer?(layer); super(layer) || layer == queue_layer; end
|
90
92
|
def queue_layer; layer_finder.queue; end
|
91
93
|
def job_layer; layer_finder.job; end
|
94
|
+
|
95
|
+
def span_trace
|
96
|
+
ScoutApm::LayerConverters::TraceConverter.
|
97
|
+
new(@context, @request, @layer_finder, @store).
|
98
|
+
call
|
99
|
+
end
|
92
100
|
end
|
93
101
|
end
|
94
102
|
end
|
@@ -11,6 +11,7 @@ module ScoutApm
|
|
11
11
|
# Let the store know we're here, and if it wants our data, it will call
|
12
12
|
# back into #call
|
13
13
|
@store.track_slow_transaction!(self)
|
14
|
+
|
14
15
|
nil # not returning anything in the layer results ... not used
|
15
16
|
end
|
16
17
|
|
@@ -51,7 +52,8 @@ module ScoutApm
|
|
51
52
|
mem_delta,
|
52
53
|
root_layer.total_allocations,
|
53
54
|
@points,
|
54
|
-
limited
|
55
|
+
limited?,
|
56
|
+
span_trace)
|
55
57
|
end
|
56
58
|
|
57
59
|
# Full metrics from this request. These get stored permanently in a SlowTransaction.
|
@@ -81,6 +83,17 @@ module ScoutApm
|
|
81
83
|
|
82
84
|
[metric_hash, allocation_metric_hash]
|
83
85
|
end
|
86
|
+
|
87
|
+
###########################################################
|
88
|
+
# Also create a new style trace. This is not a good #
|
89
|
+
# spot for this long term, but fixes an issue for now. #
|
90
|
+
###########################################################
|
91
|
+
|
92
|
+
def span_trace
|
93
|
+
ScoutApm::LayerConverters::TraceConverter.
|
94
|
+
new(@context, @request, @layer_finder, @store).
|
95
|
+
call
|
96
|
+
end
|
84
97
|
end
|
85
98
|
end
|
86
99
|
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module LayerConverters
|
3
|
+
class TraceConverter < ConverterBase
|
4
|
+
###################
|
5
|
+
# Converter API #
|
6
|
+
###################
|
7
|
+
|
8
|
+
|
9
|
+
def record!
|
10
|
+
@points = context.slow_request_policy.score(request)
|
11
|
+
|
12
|
+
# Let the store know we're here, and if it wants our data, it will call
|
13
|
+
# back into #call
|
14
|
+
@store.track_trace!(self)
|
15
|
+
|
16
|
+
nil # not returning anything in the layer results ... not used
|
17
|
+
end
|
18
|
+
|
19
|
+
#####################
|
20
|
+
# ScoreItemSet API #
|
21
|
+
#####################
|
22
|
+
def name; request.unique_name; end
|
23
|
+
def score; @points; end
|
24
|
+
|
25
|
+
# Unconditionally attempts to convert this into a DetailedTrace object.
|
26
|
+
# Can return nil if the request didn't have any scope_layer or if `timeline_traces` aren't enabled.
|
27
|
+
def call
|
28
|
+
return nil unless scope_layer
|
29
|
+
return nil unless context.config.value('timeline_traces')
|
30
|
+
|
31
|
+
# Since this request is being stored, update the needed counters
|
32
|
+
context.slow_request_policy.stored!(request)
|
33
|
+
|
34
|
+
# record the change in memory usage
|
35
|
+
mem_delta = ScoutApm::Instruments::Process::ProcessMemory.new(context).rss_to_mb(@request.capture_mem_delta!)
|
36
|
+
|
37
|
+
transaction_id = request.transaction_id
|
38
|
+
revision = context.environment.git_revision.sha
|
39
|
+
start_instant = request.root_layer.start_time
|
40
|
+
stop_instant = request.root_layer.stop_time
|
41
|
+
type = if request.web?
|
42
|
+
"Web"
|
43
|
+
elsif request.job?
|
44
|
+
"Job"
|
45
|
+
else
|
46
|
+
"Unknown"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create request tags
|
50
|
+
#
|
51
|
+
tags = {
|
52
|
+
:allocations => request.root_layer.total_allocations,
|
53
|
+
:mem_delta => mem_delta,
|
54
|
+
}.merge(request.context.to_flat_hash)
|
55
|
+
|
56
|
+
host = context.environment.hostname
|
57
|
+
path = request.annotations[:uri] || ""
|
58
|
+
code = "" # User#index for instance
|
59
|
+
|
60
|
+
spans = create_spans(request.root_layer)
|
61
|
+
|
62
|
+
DetailedTrace.new(
|
63
|
+
transaction_id,
|
64
|
+
revision,
|
65
|
+
host,
|
66
|
+
start_instant,
|
67
|
+
stop_instant,
|
68
|
+
type,
|
69
|
+
|
70
|
+
path,
|
71
|
+
code,
|
72
|
+
|
73
|
+
spans,
|
74
|
+
tags
|
75
|
+
|
76
|
+
# total_score = 0,
|
77
|
+
# percentile_score = 0,
|
78
|
+
# age_score = 0,
|
79
|
+
# memory_delta_score = 0,
|
80
|
+
# memory_allocations_score = 0
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns an array of span objects. Uses recursion to get all children
|
85
|
+
# wired up w/ correct parent_ids
|
86
|
+
def create_spans(layer, parent_id = nil)
|
87
|
+
span_id = ScoutApm::Utils::SpanId.new.to_s
|
88
|
+
|
89
|
+
start_instant = layer.start_time
|
90
|
+
stop_instant = layer.stop_time
|
91
|
+
operation = layer.legacy_metric_name
|
92
|
+
tags = {
|
93
|
+
:start_allocations => layer.allocations_start,
|
94
|
+
:stop_allocations => layer.allocations_stop,
|
95
|
+
}
|
96
|
+
if layer.desc
|
97
|
+
tags[:desc] = layer.desc.to_s
|
98
|
+
end
|
99
|
+
if layer.annotations && layer.annotations[:record_count]
|
100
|
+
tags["db.record_count"] = layer.annotations[:record_count]
|
101
|
+
end
|
102
|
+
if layer.annotations && layer.annotations[:class_name]
|
103
|
+
tags["db.class_name"] = layer.annotations[:class_name]
|
104
|
+
end
|
105
|
+
if layer.backtrace
|
106
|
+
tags[:backtrace] = backtrace_parser(layer.backtrace) rescue nil
|
107
|
+
end
|
108
|
+
|
109
|
+
# Collect up self, and all children into result array
|
110
|
+
result = []
|
111
|
+
result << DetailedTraceSpan.new(
|
112
|
+
span_id.to_s,
|
113
|
+
parent_id.to_s,
|
114
|
+
start_instant,
|
115
|
+
stop_instant,
|
116
|
+
operation,
|
117
|
+
tags)
|
118
|
+
|
119
|
+
layer.children.each do |child|
|
120
|
+
unless over_span_limit?(result)
|
121
|
+
result += create_spans(child, span_id)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
return result
|
126
|
+
end
|
127
|
+
|
128
|
+
# Take an array of ruby backtrace lines and split it into an array of hashes like:
|
129
|
+
# ["/Users/cschneid/.rvm/rubies/ruby-2.2.7/lib/ruby/2.2.0/irb/workspace.rb:86:in `eval'", ...]
|
130
|
+
# turns into:
|
131
|
+
# [ {
|
132
|
+
# "file": "app/controllers/users_controller.rb",
|
133
|
+
# "line": 10,
|
134
|
+
# "function": "index"
|
135
|
+
# },
|
136
|
+
# ]
|
137
|
+
def backtrace_parser(lines)
|
138
|
+
bt = ScoutApm::Utils::BacktraceParser.new(lines).call
|
139
|
+
|
140
|
+
bt.map do |line|
|
141
|
+
match = line.match(/(.*):(\d+):in `(.*)'/)
|
142
|
+
{
|
143
|
+
"file" => match[1],
|
144
|
+
"line" => match[2],
|
145
|
+
"function" => match[3],
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
################################################################################
|
152
|
+
# Limit Handling
|
153
|
+
################################################################################
|
154
|
+
|
155
|
+
# To prevent huge traces from being generated, we should stop collecting
|
156
|
+
# spans as we go beyond some reasonably large count.
|
157
|
+
|
158
|
+
MAX_SPANS = 2500
|
159
|
+
|
160
|
+
def over_span_limit?(spans)
|
161
|
+
if spans.size > MAX_SPANS
|
162
|
+
log_over_span_limit
|
163
|
+
@limited = true
|
164
|
+
else
|
165
|
+
false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def log_over_span_limit
|
170
|
+
unless limited?
|
171
|
+
context.logger.debug "Not recording additional spans for #{name}. Over the span limit."
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def limited?
|
176
|
+
!! @limited
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/scout_apm/reporting.rb
CHANGED
@@ -83,10 +83,11 @@ module ScoutApm
|
|
83
83
|
slow_jobs = reporting_period.slow_jobs_payload
|
84
84
|
histograms = reporting_period.histograms
|
85
85
|
db_query_metrics = reporting_period.db_query_metrics_payload
|
86
|
+
traces = (slow_transactions.map(&:span_trace) + slow_jobs.map(&:span_trace)).compact
|
86
87
|
|
87
88
|
log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
|
88
89
|
|
89
|
-
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
90
|
+
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
90
91
|
logger.debug("Sending payload w/ Headers: #{headers.inspect}")
|
91
92
|
|
92
93
|
reporter.report(payload, headers)
|
@@ -2,9 +2,9 @@
|
|
2
2
|
module ScoutApm
|
3
3
|
module Serializers
|
4
4
|
class PayloadSerializer
|
5
|
-
def self.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
5
|
+
def self.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
6
6
|
if ScoutApm::Agent.instance.context.config.value("report_format") == 'json'
|
7
|
-
ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
7
|
+
ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
8
8
|
else
|
9
9
|
metadata = metadata.dup
|
10
10
|
metadata.default = nil
|
@@ -2,7 +2,7 @@ module ScoutApm
|
|
2
2
|
module Serializers
|
3
3
|
module PayloadSerializerToJson
|
4
4
|
class << self
|
5
|
-
def serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
5
|
+
def serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
6
6
|
metadata.merge!({:payload_version => 2})
|
7
7
|
|
8
8
|
jsonify_hash({:metadata => metadata,
|
@@ -14,6 +14,7 @@ module ScoutApm
|
|
14
14
|
:db_metrics => {
|
15
15
|
:query => DbQuerySerializerToJson.new(db_query_metrics).as_json,
|
16
16
|
},
|
17
|
+
:span_traces => traces.map{ |t| t.as_json },
|
17
18
|
})
|
18
19
|
end
|
19
20
|
|
@@ -23,7 +23,9 @@ module ScoutApm
|
|
23
23
|
attr_reader :git_sha
|
24
24
|
attr_reader :truncated_metrics
|
25
25
|
|
26
|
-
|
26
|
+
attr_reader :span_trace
|
27
|
+
|
28
|
+
def initialize(agent_context, queue_name, job_name, time, total_time, exclusive_time, context, metrics, allocation_metrics, mem_delta, allocations, score, truncated_metrics, span_trace)
|
27
29
|
@queue_name = queue_name
|
28
30
|
@job_name = job_name
|
29
31
|
@time = time
|
@@ -40,6 +42,8 @@ module ScoutApm
|
|
40
42
|
@score = score
|
41
43
|
@truncated_metrics = truncated_metrics
|
42
44
|
|
45
|
+
@span_trace = span_trace
|
46
|
+
|
43
47
|
agent_context.logger.debug { "Slow Job [#{metric_name}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta}"}
|
44
48
|
end
|
45
49
|
|
@@ -13,13 +13,14 @@ module ScoutApm
|
|
13
13
|
attr_reader :prof
|
14
14
|
attr_reader :mem_delta
|
15
15
|
attr_reader :allocations
|
16
|
+
attr_reader :span_trace
|
16
17
|
attr_accessor :hostname # hack - we need to reset these server side.
|
17
18
|
attr_accessor :seconds_since_startup # hack - we need to reset these server side.
|
18
19
|
attr_accessor :git_sha # hack - we need to reset these server side.
|
19
20
|
|
20
21
|
attr_reader :truncated_metrics # True/False that says if we had to truncate the metrics of this trace
|
21
22
|
|
22
|
-
def initialize(agent_context, uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score, truncated_metrics)
|
23
|
+
def initialize(agent_context, uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score, truncated_metrics, span_trace)
|
23
24
|
@uri = uri
|
24
25
|
@metric_name = metric_name
|
25
26
|
@total_call_time = total_call_time
|
@@ -35,6 +36,7 @@ module ScoutApm
|
|
35
36
|
@score = score
|
36
37
|
@git_sha = agent_context.environment.git_revision.sha
|
37
38
|
@truncated_metrics = truncated_metrics
|
39
|
+
@span_trace = span_trace
|
38
40
|
|
39
41
|
agent_context.logger.debug { "Slow Request [#{uri}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta} Score: #{score}"}
|
40
42
|
end
|
data/lib/scout_apm/store.rb
CHANGED
@@ -42,6 +42,9 @@ module ScoutApm
|
|
42
42
|
# the name is determined from the name of the Controller or Job layer.
|
43
43
|
attr_accessor :name_override
|
44
44
|
|
45
|
+
# A unique, but otherwise meaningless String to identify this request. UUID
|
46
|
+
attr_reader :transaction_id
|
47
|
+
|
45
48
|
# When we see these layers, it means a real request is going through the
|
46
49
|
# system. We toggle a flag to turn on some slightly more expensive
|
47
50
|
# instrumentation (backtrace collection and the like) that would be too
|
@@ -64,6 +67,7 @@ module ScoutApm
|
|
64
67
|
@mem_start = mem_usage
|
65
68
|
@recorder = agent_context.recorder
|
66
69
|
@real_request = false
|
70
|
+
@transaction_id = ScoutApm::Utils::TransactionId.new.to_s
|
67
71
|
ignore_request! if @recorder.nil?
|
68
72
|
end
|
69
73
|
|
@@ -275,6 +279,8 @@ module ScoutApm
|
|
275
279
|
|
276
280
|
@agent_context.transaction_time_consumed.add(unique_name, root_layer.total_call_time)
|
277
281
|
|
282
|
+
context.add(:transaction_id => transaction_id)
|
283
|
+
|
278
284
|
# Make a constant, then call converters.dup.each so it isn't inline?
|
279
285
|
converters = {
|
280
286
|
:histograms => LayerConverters::Histograms,
|
@@ -287,6 +293,11 @@ module ScoutApm
|
|
287
293
|
|
288
294
|
:slow_job => LayerConverters::SlowJobConverter,
|
289
295
|
:slow_req => LayerConverters::SlowRequestConverter,
|
296
|
+
|
297
|
+
# This is now integrated into the slow_job and slow_req converters, so that
|
298
|
+
# we get the exact same set of traces either way. We can call it
|
299
|
+
# directly when we move away from the legacy trace styles.
|
300
|
+
# :traces => LayerConverters::TraceConverter,
|
290
301
|
}
|
291
302
|
|
292
303
|
walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
|
@@ -11,5 +11,32 @@ module ScoutApm
|
|
11
11
|
s
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
# Represents a random ID that we can use to track a certain transaction.
|
16
|
+
# The `trans` prefix is only for ease of reading logs - it should not be
|
17
|
+
# interpreted to convey any sort of meaning.
|
18
|
+
class TransactionId
|
19
|
+
def initialize
|
20
|
+
@random = SecureRandom.hex(16)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"trans-#{@random}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Represents a random ID that we can use to track a certain span. The
|
29
|
+
# `span` prefix is only for ease of reading logs - it should not be
|
30
|
+
# interpreted to convey any sort of meaning.
|
31
|
+
class SpanId
|
32
|
+
def initialize
|
33
|
+
@random = SecureRandom.hex(16)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"span-#{@random}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
14
41
|
end
|
15
42
|
end
|
data/lib/scout_apm/version.rb
CHANGED
@@ -8,7 +8,7 @@ class PayloadSerializerTest < Minitest::Test
|
|
8
8
|
:unique_id => "unique_idz",
|
9
9
|
:agent_version => 123
|
10
10
|
}
|
11
|
-
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {})
|
11
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {}, [])
|
12
12
|
|
13
13
|
# symbol keys turn to strings
|
14
14
|
formatted_metadata = {
|
@@ -49,7 +49,7 @@ class PayloadSerializerTest < Minitest::Test
|
|
49
49
|
stats.total_exclusive_time = 0.078132088
|
50
50
|
}
|
51
51
|
}
|
52
|
-
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {}, [], [], [], {})
|
52
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {}, [], [], [], {}, [])
|
53
53
|
formatted_metrics = [
|
54
54
|
{
|
55
55
|
"key" => {
|
@@ -94,7 +94,7 @@ class PayloadSerializerTest < Minitest::Test
|
|
94
94
|
:quotie => "here are some \"quotes\"",
|
95
95
|
:payload_version => 2,
|
96
96
|
}
|
97
|
-
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {})
|
97
|
+
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {}, [])
|
98
98
|
|
99
99
|
# symbol keys turn to strings
|
100
100
|
formatted_metadata = {
|
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: 2.
|
4
|
+
version: 2.5.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: 2019-
|
12
|
+
date: 2019-06-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -210,6 +210,7 @@ files:
|
|
210
210
|
- lib/scout_apm/db_query_metric_set.rb
|
211
211
|
- lib/scout_apm/db_query_metric_stats.rb
|
212
212
|
- lib/scout_apm/debug.rb
|
213
|
+
- lib/scout_apm/detailed_trace.rb
|
213
214
|
- lib/scout_apm/environment.rb
|
214
215
|
- lib/scout_apm/extensions/config.rb
|
215
216
|
- lib/scout_apm/extensions/transaction_callback_payload.rb
|
@@ -264,6 +265,7 @@ files:
|
|
264
265
|
- lib/scout_apm/layer_converters/request_queue_time_converter.rb
|
265
266
|
- lib/scout_apm/layer_converters/slow_job_converter.rb
|
266
267
|
- lib/scout_apm/layer_converters/slow_request_converter.rb
|
268
|
+
- lib/scout_apm/layer_converters/trace_converter.rb
|
267
269
|
- lib/scout_apm/limited_layer.rb
|
268
270
|
- lib/scout_apm/logger.rb
|
269
271
|
- lib/scout_apm/metric_meta.rb
|
@@ -392,7 +394,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
392
394
|
version: '0'
|
393
395
|
requirements: []
|
394
396
|
rubyforge_project: scout_apm
|
395
|
-
rubygems_version: 2.6
|
397
|
+
rubygems_version: 2.4.6
|
396
398
|
signing_key:
|
397
399
|
specification_version: 4
|
398
400
|
summary: Ruby application performance monitoring
|