Class: ScoutApm::TrackedRequest

Inherits:
Object
  • Object
show all
Defined in:
lib/scout_apm/tracked_request.rb

Constant Summary collapse

BACKTRACE_THRESHOLD =

the minimum threshold in seconds to record the backtrace for a metric.

0.5
BACKTRACE_BLACKLIST =
["Controller", "Job"]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTrackedRequest

Returns a new instance of TrackedRequest.



40
41
42
43
44
45
46
47
48
# File 'lib/scout_apm/tracked_request.rb', line 40

def initialize
  @layers = []
  @call_counts = Hash.new { |h, k| h[k] = CallSet.new }
  @annotations = {}
  @ignoring_children = false
  @context = Context.new
  @root_layer = nil
  @error = false
end

Instance Attribute Details

#annotationsObject (readonly)

As we go through a request, instrumentation can mark more general data into the Request Known Keys:

:uri - the full URI requested by the user
:queue_latency - how long a background Job spent in the queue before starting processing


23
24
25
# File 'lib/scout_apm/tracked_request.rb', line 23

def annotations
  @annotations
end

#call_countsObject

This maintains a lookup hash of Layer names and call counts. It’s used to trigger fetching a backtrace on n+1 calls. Note that layer names might not be Strings - can alse be Utils::ActiveRecordMetricName. Also, this would fail for layers with same names across multiple types.



36
37
38
# File 'lib/scout_apm/tracked_request.rb', line 36

def call_counts
  @call_counts
end

#contextObject (readonly)

Context is application defined extra information. (ie, which user, what is their email/ip, what plan are they on, what locale are they using, etc) See documentation for examples on how to set this from a before_filter



13
14
15
# File 'lib/scout_apm/tracked_request.rb', line 13

def context
  @context
end

#headersObject (readonly)

Headers as recorded by rails Can be nil if we never reach a Rails Controller



27
28
29
# File 'lib/scout_apm/tracked_request.rb', line 27

def headers
  @headers
end

#request_typeObject (readonly)

What kind of request is this? A trace of a web request, or a background job? Use job! and web! to set, and job? and web? to query



31
32
33
# File 'lib/scout_apm/tracked_request.rb', line 31

def request_type
  @request_type
end

#root_layerObject (readonly)

The first layer registered with this request. All other layers will be children of this layer.



17
18
19
# File 'lib/scout_apm/tracked_request.rb', line 17

def root_layer
  @root_layer
end

Instance Method Details

#acknowledge_children!Object



245
246
247
# File 'lib/scout_apm/tracked_request.rb', line 245

def acknowledge_children!
  @ignoring_children = false
end

#annotate_request(hsh) ⇒ Object

As we learn things about this request, we can add data here. For instance, when we know where Rails routed this request to, we can store that scope info. Or as soon as we know which URI it was directed at, we can store that.

This data is internal to ScoutApm, to add custom information, use the Context api.



135
136
137
# File 'lib/scout_apm/tracked_request.rb', line 135

def annotate_request(hsh)
  @annotations.merge!(hsh)
end

#capture_backtrace?(layer) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/scout_apm/tracked_request.rb', line 77

def capture_backtrace?(layer)
  # Never capture backtraces for this kind of layer. The backtrace will
  # always be 100% framework code.
  return false if BACKTRACE_BLACKLIST.include?(layer.type)

  # Only capture backtraces if we're in a real "request". Otherwise we
  # can spend lot of time capturing backtraces from the internals of
  # Sidekiq, only to throw them away immediately.
  return false unless (web? || job?)

  # Capture any individually slow layer.
  return true if layer.total_exclusive_time > BACKTRACE_THRESHOLD

  # Capture any layer that we've seen many times. Captures n+1 problems
  return true if @call_counts[layer.name].capture_backtrace?

  # Don't capture otherwise
  false
end

#error!Object

This request had an exception. Mark it down as an error



140
141
142
# File 'lib/scout_apm/tracked_request.rb', line 140

def error!
  @error = true
end

#error?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'lib/scout_apm/tracked_request.rb', line 144

def error?
  @error
end

#finalized?Boolean

Are we finished with this request? We’re done if we have no layers left after popping one off

Returns:

  • (Boolean)


108
109
110
# File 'lib/scout_apm/tracked_request.rb', line 108

def finalized?
  @layers.none?
end

#ignore_children!Object

Enable this when you would otherwise double track something interesting. This came up when we implemented InfluxDB instrumentation, which is more specific, and useful than the fact that InfluxDB happens to use Net::HTTP internally

When enabled, new layers won’t be added to the current Request.

Do not forget to turn if off when leaving a layer, it is the instrumentation’s task to do that.



241
242
243
# File 'lib/scout_apm/tracked_request.rb', line 241

def ignore_children!
  @ignoring_children = true
end

#ignoring_children?Boolean

Returns:

  • (Boolean)


249
250
251
# File 'lib/scout_apm/tracked_request.rb', line 249

def ignoring_children?
  @ignoring_children
end

#job!Object



152
153
154
# File 'lib/scout_apm/tracked_request.rb', line 152

def job!
  @request_type = "job"
end

#job?Boolean

Returns:

  • (Boolean)


156
157
158
# File 'lib/scout_apm/tracked_request.rb', line 156

def job?
  request_type == "job"
end

#record!Object

Convert this request to the appropriate structure, then report it into the peristent Store object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/scout_apm/tracked_request.rb', line 174

def record!
  @recorded = true

  # Update immediate and long-term histograms for both job and web requests
  if unique_name != :unknown
    ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
    ScoutApm::Agent.instance.request_histograms_by_time[ScoutApm::Agent.instance.store.current_timestamp].
      add(unique_name, root_layer.total_call_time)
  end

  metrics = LayerConverters::MetricConverter.new(self).call
  ScoutApm::Agent.instance.store.track!(metrics)

  error_metrics = LayerConverters::ErrorConverter.new(self).call
  ScoutApm::Agent.instance.store.track!(error_metrics)

  if web?
    # Don't #call this - that's the job of the ScoredItemSet later.
    slow_converter = LayerConverters::SlowRequestConverter.new(self)
    ScoutApm::Agent.instance.store.track_slow_transaction!(slow_converter)

    queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
    ScoutApm::Agent.instance.store.track!(queue_time_metrics)
  end

  if job?
    job_metrics = LayerConverters::JobConverter.new(self).call
    ScoutApm::Agent.instance.store.track_job!(job_metrics)

    job_converter = LayerConverters::SlowJobConverter.new(self)
    ScoutApm::Agent.instance.store.track_slow_job!(job_converter)
  end
end

#recorded?Boolean

Have we already persisted this request? Used to know when we should just create a new one (don’t attempt to add data to an already-recorded request). See RequestManager

Returns:

  • (Boolean)


223
224
225
# File 'lib/scout_apm/tracked_request.rb', line 223

def recorded?
  @recorded
end

#set_headers(headers) ⇒ Object



148
149
150
# File 'lib/scout_apm/tracked_request.rb', line 148

def set_headers(headers)
  @headers = headers
end

#start_layer(layer) ⇒ Object



50
51
52
53
54
55
56
57
58
59
# File 'lib/scout_apm/tracked_request.rb', line 50

def start_layer(layer)
  if ignoring_children?
    return
  end

  start_request(layer) unless @root_layer
  update_call_counts!(layer)
  @layers[-1].add_child(layer) if @layers.any?
  @layers.push(layer)
end

#start_request(layer) ⇒ Object

Run at the beginning of the whole request

  • Capture the first layer as the root_layer



115
116
117
# File 'lib/scout_apm/tracked_request.rb', line 115

def start_request(layer)
  @root_layer = layer unless @root_layer # capture root layer
end

#stop_layerObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/scout_apm/tracked_request.rb', line 61

def stop_layer
  return if ignoring_children?

  layer = @layers.pop
  layer.record_stop_time!

  if capture_backtrace?(layer)
    layer.capture_backtrace!
  end

  if finalized?
    stop_request
  end
end

#stop_requestObject

Run at the end of the whole request

  • Send the request off to be stored



122
123
124
# File 'lib/scout_apm/tracked_request.rb', line 122

def stop_request
  record!
end

#unique_nameObject

Only call this after the request is complete



209
210
211
212
213
214
215
216
217
218
# File 'lib/scout_apm/tracked_request.rb', line 209

def unique_name
  @unique_name ||= begin
                     scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
                     if scope_layer
                       scope_layer.legacy_metric_name
                     else
                       :unknown
                     end
                   end
end

#update_call_counts!(layer) ⇒ Object

Maintains a lookup Hash of call counts by layer name. Used to determine if we should capture a backtrace.



98
99
100
# File 'lib/scout_apm/tracked_request.rb', line 98

def update_call_counts!(layer)
  @call_counts[layer.name].update!(layer.desc)
end

#web!Object



160
161
162
# File 'lib/scout_apm/tracked_request.rb', line 160

def web!
  @request_type = "web"
end

#web?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/scout_apm/tracked_request.rb', line 164

def web?
  request_type == "web"
end