Class: ScoutApm::TrackedRequest
- Inherits:
-
Object
- Object
- ScoutApm::TrackedRequest
- Defined in:
- lib/scout_apm/tracked_request.rb
Constant Summary collapse
- BACKTRACE_BLACKLIST =
["Controller", "Job"]
Instance Attribute Summary collapse
-
#annotations ⇒ Object
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.
-
#call_counts ⇒ Object
This maintains a lookup hash of Layer names and call counts.
-
#context ⇒ Object
readonly
Context is application defined extra information.
-
#dev_trace ⇒ Object
Whereas the instant_key gets set per-request in reponse to a URL param, dev_trace is set in the config file.
-
#headers ⇒ Object
readonly
Headers as recorded by rails Can be nil if we never reach a Rails Controller.
-
#instant_key ⇒ Object
if there’s an instant_key, pass the transaction trace on for immediate reporting (in addition to the usual background aggregation) this is set in the controller instumentation (ActionControllerRails3Rails4 according).
-
#request_type ⇒ Object
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.
-
#root_layer ⇒ Object
readonly
The first layer registered with this request.
Instance Method Summary collapse
- #acknowledge_children! ⇒ Object
-
#annotate_request(hsh) ⇒ Object
As we learn things about this request, we can add data here.
-
#backtrace_threshold ⇒ Object
Grab backtraces more aggressively when running in dev trace mode.
- #capture_backtrace?(layer) ⇒ Boolean
- #capture_mem_delta! ⇒ Object
-
#current_layer ⇒ Object
Grab the currently running layer.
-
#error! ⇒ Object
This request had an exception.
- #error? ⇒ Boolean
-
#finalized? ⇒ Boolean
Are we finished with this request? We’re done if we have no layers left after popping one off.
-
#ignore_children! ⇒ Object
Enable this when you would otherwise double track something interesting.
- #ignoring_children? ⇒ Boolean
-
#initialize(store) ⇒ TrackedRequest
constructor
A new instance of TrackedRequest.
- #instant? ⇒ Boolean
- #job! ⇒ Object
- #job? ⇒ Boolean
-
#mem_usage ⇒ Object
This may be in bytes or KB based on the OSX.
-
#record! ⇒ Object
Convert this request to the appropriate structure, then report it into the peristent Store object.
-
#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).
- #set_headers(headers) ⇒ Object
- #start_layer(layer) ⇒ Object
-
#start_request(layer) ⇒ Object
Run at the beginning of the whole request.
- #stop_layer ⇒ Object
-
#stop_request ⇒ Object
Run at the end of the whole request.
-
#unique_name ⇒ Object
Only call this after the request is complete.
-
#update_call_counts!(layer) ⇒ Object
Maintains a lookup Hash of call counts by layer name.
- #web! ⇒ Object
- #web? ⇒ Boolean
Constructor Details
#initialize(store) ⇒ TrackedRequest
Returns a new instance of TrackedRequest.
45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/scout_apm/tracked_request.rb', line 45 def initialize(store) @store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only) @layers = [] @call_set = Hash.new { |h, k| h[k] = CallSet.new } @annotations = {} @ignoring_children = 0 @context = Context.new @root_layer = nil @error = false @instant_key = nil @mem_start = mem_usage @dev_trace = ScoutApm::Agent.instance.config.value('dev_trace') && Rails.env.development? end |
Instance Attribute Details
#annotations ⇒ Object (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_counts ⇒ Object
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 |
#context ⇒ Object (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 |
#dev_trace ⇒ Object
Whereas the instant_key gets set per-request in reponse to a URL param, dev_trace is set in the config file
43 44 45 |
# File 'lib/scout_apm/tracked_request.rb', line 43 def dev_trace @dev_trace end |
#headers ⇒ Object (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 |
#instant_key ⇒ Object
if there’s an instant_key, pass the transaction trace on for immediate reporting (in addition to the usual background aggregation) this is set in the controller instumentation (ActionControllerRails3Rails4 according)
40 41 42 |
# File 'lib/scout_apm/tracked_request.rb', line 40 def instant_key @instant_key end |
#request_type ⇒ Object (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_layer ⇒ Object (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
322 323 324 325 326 |
# File 'lib/scout_apm/tracked_request.rb', line 322 def acknowledge_children! if @ignoring_children > 0 @ignoring_children -= 1 end 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.
179 180 181 |
# File 'lib/scout_apm/tracked_request.rb', line 179 def annotate_request(hsh) @annotations.merge!(hsh) end |
#backtrace_threshold ⇒ Object
Grab backtraces more aggressively when running in dev trace mode
333 334 335 |
# File 'lib/scout_apm/tracked_request.rb', line 333 def backtrace_threshold dev_trace ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric. end |
#capture_backtrace?(layer) ⇒ Boolean
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/scout_apm/tracked_request.rb', line 112 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_set[layer.name].capture_backtrace? # Don't capture otherwise false end |
#capture_mem_delta! ⇒ Object
142 143 144 |
# File 'lib/scout_apm/tracked_request.rb', line 142 def capture_mem_delta! @mem_delta = mem_usage - @mem_start end |
#current_layer ⇒ Object
Grab the currently running layer. Useful for adding additional data as we learn it. This is useful in ActiveRecord instruments, where we start the instrumentation early, and gradually learn more about the request that actually happened as we go (for instance, the # of records found, or the actual SQL generated).
Returns nil in the case there is no current layer. That would be normal for a completed TrackedRequest
107 108 109 |
# File 'lib/scout_apm/tracked_request.rb', line 107 def current_layer @layers.last end |
#error! ⇒ Object
This request had an exception. Mark it down as an error
184 185 186 |
# File 'lib/scout_apm/tracked_request.rb', line 184 def error! @error = true end |
#error? ⇒ Boolean
188 189 190 |
# File 'lib/scout_apm/tracked_request.rb', line 188 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
152 153 154 |
# File 'lib/scout_apm/tracked_request.rb', line 152 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, and calls to stop_layer will be ignored.
Do not forget to turn if off when leaving a layer, it is the instrumentation’s task to do that.
When you use this in code, be sure to use it in this order:
start_layer ignore_children
-> call
acknowledge_children stop_layer
If you don’t call it in this order, it’s possible to get out of sync, and have an ignored start and an actually-executed stop, causing layers to get out of sync
318 319 320 |
# File 'lib/scout_apm/tracked_request.rb', line 318 def ignore_children! @ignoring_children += 1 end |
#ignoring_children? ⇒ Boolean
328 329 330 |
# File 'lib/scout_apm/tracked_request.rb', line 328 def ignoring_children? @ignoring_children > 0 end |
#instant? ⇒ Boolean
212 213 214 |
# File 'lib/scout_apm/tracked_request.rb', line 212 def instant? instant_key end |
#job! ⇒ Object
196 197 198 |
# File 'lib/scout_apm/tracked_request.rb', line 196 def job! @request_type = "job" end |
#job? ⇒ Boolean
200 201 202 |
# File 'lib/scout_apm/tracked_request.rb', line 200 def job? request_type == "job" end |
#mem_usage ⇒ Object
This may be in bytes or KB based on the OSX. We store this as-is here and only do conversion to MB in Layer Converters.
138 139 140 |
# File 'lib/scout_apm/tracked_request.rb', line 138 def mem_usage ScoutApm::Instruments::Process::ProcessMemory.rss end |
#record! ⇒ Object
Convert this request to the appropriate structure, then report it into the peristent Store object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/scout_apm/tracked_request.rb', line 222 def record! @recorded = true # Bail out early if the user asked us to ignore this uri return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri]) # 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[@store.]. add(unique_name, root_layer.total_call_time) end metrics = LayerConverters::MetricConverter.new(self).call @store.track!(metrics) error_metrics = LayerConverters::ErrorConverter.new(self).call @store.track!(error_metrics) allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call @store.track!(allocation_metrics) if web? # Don't #call this - that's the job of the ScoredItemSet later. slow_converter = LayerConverters::SlowRequestConverter.new(self) @store.track_slow_transaction!(slow_converter) queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call @store.track!(queue_time_metrics) # If there's an instant_key, it means we need to report this right away if instant? trace = slow_converter.call ScoutApm::InstantReporting.new(trace, instant_key).call() end end if job? job_metrics = LayerConverters::JobConverter.new(self).call @store.track_job!(job_metrics) job_converter = LayerConverters::SlowJobConverter.new(self) @store.track_slow_job!(job_converter) end allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call @store.track!(allocation_metrics) 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
287 288 289 |
# File 'lib/scout_apm/tracked_request.rb', line 287 def recorded? @recorded end |
#set_headers(headers) ⇒ Object
192 193 194 |
# File 'lib/scout_apm/tracked_request.rb', line 192 def set_headers(headers) @headers = headers end |
#start_layer(layer) ⇒ Object
59 60 61 62 63 64 65 66 67 |
# File 'lib/scout_apm/tracked_request.rb', line 59 def start_layer(layer) if ignoring_children? return end start_request(layer) unless @root_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
159 160 161 |
# File 'lib/scout_apm/tracked_request.rb', line 159 def start_request(layer) @root_layer = layer unless @root_layer # capture root layer end |
#stop_layer ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/scout_apm/tracked_request.rb', line 69 def stop_layer return if ignoring_children? layer = @layers.pop # Safeguard against a mismatch in the layer tracking in an instrument. # This class works under the assumption that start & stop layers are # lined up correctly. If stop_layer gets called twice, when it should # only have been called once you'll end up with this error. if layer.nil? ScoutApm::Agent.instance.logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}") stop_request return end layer.record_stop_time! layer.record_allocations! # This must be called before checking if a backtrace should be collected as the call count influences our capture logic. # We call `#update_call_counts in stop layer to ensure the layer has a final desc. Layer#desc is updated during the AR instrumentation flow. update_call_counts!(layer) if capture_backtrace?(layer) layer.capture_backtrace! end if finalized? stop_request end end |
#stop_request ⇒ Object
Run at the end of the whole request
-
Send the request off to be stored
166 167 168 |
# File 'lib/scout_apm/tracked_request.rb', line 166 def stop_request record! end |
#unique_name ⇒ Object
Only call this after the request is complete
273 274 275 276 277 278 279 280 281 282 |
# File 'lib/scout_apm/tracked_request.rb', line 273 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.
133 134 135 |
# File 'lib/scout_apm/tracked_request.rb', line 133 def update_call_counts!(layer) @call_set[layer.name].update!(layer.desc) end |
#web! ⇒ Object
204 205 206 |
# File 'lib/scout_apm/tracked_request.rb', line 204 def web! @request_type = "web" end |
#web? ⇒ Boolean
208 209 210 |
# File 'lib/scout_apm/tracked_request.rb', line 208 def web? request_type == "web" end |