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 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