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