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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3876e11520a259f36044e5363edcb5aeeb4b21fa
4
- data.tar.gz: cc1b0079ab39e11f40a864c49ef2fbe47047b186
3
+ metadata.gz: 2003c0ac2f53ba79444a99beff51200791cadd27
4
+ data.tar.gz: aaa6bac945f0c541a39e6326f79a1bbd4959a663
5
5
  SHA512:
6
- metadata.gz: d027fd21baa97648867063ec267c53f1dab7cf1060c5f13a9c69f1ce70b89a78465fe0fdda20ddc4d44ea6b281c5673f3661fb0cb7f3b34982e4081e45041942
7
- data.tar.gz: aee3351685dc70a95857ffc63bc8dfad81e6414ddb53146eaca3483fc0bd94ef29024da47079f62bc1d568cbc979afc20d6f960fa53e25b123136202fd10badd
6
+ metadata.gz: 516804f4427ca84b89ae752964d6e38653c29698c1f833b4facec0ed784dde8e5212c11fdca62fc6f837a94a5cebabcd68130331f89816374881901d5382f750
7
+ data.tar.gz: 07f51e83d3aa6b79d883113b430fd20a0814dcae613285be6f3547a588407f7aa5eb02d9b5972bf01b4c4ca8aaf3a9f2191ce0025afa016780d7a05caf65310c
@@ -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).
@@ -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://help.apm.scoutapp.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://help.apm.scoutapp.com/#scoutprof) (BETA).
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://apm.scoutapp.com) account and put the provided
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://help.apm.scoutapp.com/).
70
+ and more, see our [help site](https://docs.scoutapm.com/).
68
71
 
69
72
  ## Help
70
73
 
@@ -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://help.apm.scoutapp.com/#ruby-configuration-options")
208
+ "https://docs.scoutapm.com/#ruby-configuration-options")
209
209
  end
210
210
  end
211
211
  end
@@ -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://help.apm.scoutapp.com for
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
+
@@ -17,6 +17,9 @@ module ScoutApm
17
17
  def track_one!(type, name, value, options={})
18
18
  end
19
19
 
20
+ def track_trace!(trace, type)
21
+ end
22
+
20
23
  def track_histograms!(histograms, options={})
21
24
  end
22
25
 
@@ -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
@@ -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
- def initialize(agent_context, queue_name, job_name, time, total_time, exclusive_time, context, metrics, allocation_metrics, mem_delta, allocations, score, truncated_metrics)
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
@@ -189,7 +189,6 @@ module ScoutApm
189
189
 
190
190
  # One period of Storage. Typically 1 minute
191
191
  class StoreReportingPeriod
192
-
193
192
  # A ScoredItemSet holding the "best" traces for the period
194
193
  attr_reader :request_traces
195
194
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "2.4.24"
2
+ VERSION = "2.5.0"
3
3
  end
@@ -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.24
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-03-15 00:00:00.000000000 Z
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.14.3
397
+ rubygems_version: 2.4.6
396
398
  signing_key:
397
399
  specification_version: 4
398
400
  summary: Ruby application performance monitoring