template_streaming 0.0.11 → 0.1.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.
- data/CHANGELOG +9 -0
- data/README.markdown +177 -88
- data/Rakefile +0 -21
- data/lib/template_streaming.rb +184 -99
- data/lib/template_streaming/autoflushing.rb +88 -0
- data/lib/template_streaming/caching.rb +68 -0
- data/lib/template_streaming/error_recovery.rb +199 -85
- data/lib/template_streaming/new_relic.rb +555 -0
- data/lib/template_streaming/templates/errors.erb +37 -0
- data/lib/template_streaming/version.rb +1 -1
- data/rails/init.rb +3 -0
- data/spec/autoflushing_spec.rb +75 -0
- data/spec/caching_spec.rb +126 -0
- data/spec/error_recovery_spec.rb +261 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/streaming_app.rb +135 -0
- data/spec/template_streaming_spec.rb +926 -0
- metadata +55 -27
@@ -0,0 +1,555 @@
|
|
1
|
+
#
|
2
|
+
# The NewRelic agent won't consider activity outside of the
|
3
|
+
# controller's #perform_action inside the scope of the request. We
|
4
|
+
# need to add the rendering that occurs in the response body's #each
|
5
|
+
# to the stats and traces.
|
6
|
+
#
|
7
|
+
# The New Relic agent offers no useful API for this, so we resort to
|
8
|
+
# fugly, brittle hacks. Hopefully this will improve in a later version
|
9
|
+
# of the agent.
|
10
|
+
#
|
11
|
+
# Here's what we do:
|
12
|
+
#
|
13
|
+
# Stats:
|
14
|
+
#
|
15
|
+
# * We sandwich the action and view rendering between
|
16
|
+
# Agent#start_accumulating and Agent#finish_accumulating.
|
17
|
+
# * During this, the stats for the metric names passed
|
18
|
+
# to #start_accumulating will be returned from the StatsEngine as
|
19
|
+
# AccumulatedMethodTraceStats objects. These are stored outside of
|
20
|
+
# the usual stats table.
|
21
|
+
# * On #finish_accumulating, the AccumulatedMethodTraceStats
|
22
|
+
# calculate the accumulated values and add them to the standard
|
23
|
+
# stats table.
|
24
|
+
# * We ensure the view stats are in the correct scope by storing the
|
25
|
+
# metric_frame created during the controller's #perform_action,
|
26
|
+
# and opening a new metric frame with attributes copied from the
|
27
|
+
# saved metric frame.
|
28
|
+
#
|
29
|
+
# Apdex:
|
30
|
+
#
|
31
|
+
# * We stash the first metric frame in the env hash, and tell it not
|
32
|
+
# to submit apdex values (#hold_apdex). Instead, it records the
|
33
|
+
# times that would have been used in the apdex calculation.
|
34
|
+
# * After body.each, we pass the first metric frame to the second to
|
35
|
+
# accumulate the times for the apdex stat
|
36
|
+
# (#record_accumulated_apdex)
|
37
|
+
#
|
38
|
+
# Histogram:
|
39
|
+
#
|
40
|
+
# * We intercept calls to Histogram#process(time) between
|
41
|
+
# #start_accumulating and #finish_accumulating.
|
42
|
+
# * On #finish_accumulating, we call the standard Histogram#process
|
43
|
+
# to add the histogram stat.
|
44
|
+
# * Because Agent#reset_stats replaces Agent#histogram with a fresh
|
45
|
+
# instance, and this happens in a second thread outside of a
|
46
|
+
# critical section, we can't store the accumulating time in the
|
47
|
+
# histogram. We instead store it in the agent.
|
48
|
+
#
|
49
|
+
# Traces:
|
50
|
+
#
|
51
|
+
# * We intercept TransactionSampler#notice_scope_empty to stash the
|
52
|
+
# completed samples in an array of accumulated samples.
|
53
|
+
# * On #finish_accumulating, we merge the samples into a
|
54
|
+
# supersample, which replaces the root segments of the accumulated
|
55
|
+
# samples with one common root segment.
|
56
|
+
# * The supersample is added to the list for harvesting.
|
57
|
+
#
|
58
|
+
# TODO
|
59
|
+
# ----
|
60
|
+
#
|
61
|
+
# * Add support for New Relic developer mode profiling.
|
62
|
+
#
|
63
|
+
|
64
|
+
# Load parts of the agent we need to hack.
|
65
|
+
def self.expand_load_path_entry(path)
|
66
|
+
$:.each do |dir|
|
67
|
+
absolute_path = File.join(dir, path)
|
68
|
+
return absolute_path if File.exist?(absolute_path)
|
69
|
+
end
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
require 'new_relic/agent'
|
73
|
+
# New Relic requires this thing multiple times under different names...
|
74
|
+
require 'new_relic/agent/instrumentation/metric_frame'
|
75
|
+
require expand_load_path_entry('new_relic/agent/instrumentation/metric_frame.rb')
|
76
|
+
require 'new_relic/agent/instrumentation/controller_instrumentation'
|
77
|
+
|
78
|
+
module TemplateStreaming
|
79
|
+
module NewRelic
|
80
|
+
Error = Class.new(RuntimeError)
|
81
|
+
|
82
|
+
# Rack environment keys.
|
83
|
+
ENV_FRAME_DATA = 'template_streaming.new_relic.frame_data'
|
84
|
+
ENV_RECORDED_METRICS = 'template_streaming.new_relic.recorded_metrics'
|
85
|
+
ENV_METRIC_PATH = 'template_streaming.new_relic.metric_path'
|
86
|
+
ENV_IGNORE_APDEX = 'template_streaming.new_relic.ignore_apdex'
|
87
|
+
|
88
|
+
class Middleware
|
89
|
+
def initialize(app)
|
90
|
+
@app = app
|
91
|
+
end
|
92
|
+
|
93
|
+
def call(env)
|
94
|
+
@env = env
|
95
|
+
status, headers, @body = @app.call(env)
|
96
|
+
[status, headers, self]
|
97
|
+
rescue Exception => error
|
98
|
+
agent.finish_accumulating
|
99
|
+
raise
|
100
|
+
end
|
101
|
+
|
102
|
+
def each(&block)
|
103
|
+
in_controller_scope do
|
104
|
+
@body.each(&block)
|
105
|
+
end
|
106
|
+
ensure
|
107
|
+
agent.finish_accumulating
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def agent
|
113
|
+
::NewRelic::Agent.instance
|
114
|
+
end
|
115
|
+
|
116
|
+
def in_controller_scope
|
117
|
+
controller_frame_data = @env[ENV_FRAME_DATA] or
|
118
|
+
# Didn't hit the action, or do_not_trace was set.
|
119
|
+
return yield
|
120
|
+
|
121
|
+
#return perform_action_with_newrelic_profile(args, &block) if NewRelic::Control.instance.profiling?
|
122
|
+
|
123
|
+
# This is based on ControllerInstrumentation#perform_action_with_newrelic_trace.
|
124
|
+
frame_data = ::NewRelic::Agent::Instrumentation::MetricFrame.current(true)
|
125
|
+
frame_data.apdex_start = frame_data.start
|
126
|
+
frame_data.request = controller_frame_data.request
|
127
|
+
frame_data.push('Controller', @env[ENV_METRIC_PATH])
|
128
|
+
begin
|
129
|
+
frame_data.filtered_params = controller_frame_data.filtered_params
|
130
|
+
::NewRelic::Agent.trace_execution_scoped(@env[ENV_RECORDED_METRICS]) do
|
131
|
+
begin
|
132
|
+
frame_data.start_transaction
|
133
|
+
::NewRelic::Agent::BusyCalculator.dispatcher_start frame_data.start
|
134
|
+
yield
|
135
|
+
rescue Exception => e
|
136
|
+
frame_data.notice_error(e)
|
137
|
+
raise
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
::NewRelic::Agent::BusyCalculator.dispatcher_finish
|
142
|
+
frame_data.record_accumulated_apdex(controller_frame_data) unless @env[ENV_IGNORE_APDEX]
|
143
|
+
frame_data.pop
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module Controller
|
149
|
+
def self.included(base)
|
150
|
+
base.class_eval do
|
151
|
+
# Make sure New Relic's hook wraps ours so we have access to the metric frame it sets.
|
152
|
+
method_name = :perform_action_with_newrelic_trace
|
153
|
+
method_defined?(method_name) || private_method_defined?(method_name) and
|
154
|
+
raise "Template Streaming must be loaded before New Relic's controller instrumentation"
|
155
|
+
alias_method_chain :process, :template_streaming
|
156
|
+
alias_method_chain :perform_action, :template_streaming
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def process_with_template_streaming(request, response, method = :perform_action, *arguments)
|
161
|
+
metric_names = ["HttpDispatcher", "Controller/#{newrelic_metric_path(request.parameters['action'])}"]
|
162
|
+
::NewRelic::Agent.instance.start_accumulating(*metric_names)
|
163
|
+
process_without_template_streaming(request, response, method, *arguments)
|
164
|
+
end
|
165
|
+
|
166
|
+
def perform_action_with_template_streaming(*args, &block)
|
167
|
+
unless _is_filtered?('do_not_trace')
|
168
|
+
frame_data = request.env[ENV_FRAME_DATA] = ::NewRelic::Agent::Instrumentation::MetricFrame.current
|
169
|
+
frame_data.hold_apdex
|
170
|
+
# This depends on current scope stack, so stash it too.
|
171
|
+
request.env[ENV_RECORDED_METRICS] = ::NewRelic::Agent::Instrumentation::MetricFrame.current.recorded_metrics
|
172
|
+
request.env[ENV_METRIC_PATH] = newrelic_metric_path
|
173
|
+
request.env[ENV_IGNORE_APDEX] = _is_filtered?('ignore_apdex')
|
174
|
+
end
|
175
|
+
perform_action_without_template_streaming(*args, &block)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
module StatsEngine
|
180
|
+
def self.included(base)
|
181
|
+
base.class_eval do
|
182
|
+
alias_method_chain :get_stats_no_scope, :template_streaming
|
183
|
+
alias_method_chain :get_custom_stats, :template_streaming
|
184
|
+
alias_method_chain :get_stats, :template_streaming
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Start accumulating the given +metric_names+.
|
190
|
+
#
|
191
|
+
# The metric_names can be either strings or MetricSpec's. See
|
192
|
+
# StatsEngine::MetricStats for which metric names you need to
|
193
|
+
# accumulate.
|
194
|
+
#
|
195
|
+
def start_accumulating(*metric_names)
|
196
|
+
metric_names.each do |metric_name|
|
197
|
+
unaccumulated_stats = stats_hash[metric_name] ||= ::NewRelic::MethodTraceStats.new
|
198
|
+
accumulated_stats = AccumulatedMethodTraceStats.new(unaccumulated_stats)
|
199
|
+
accumulated_stats_hash[metric_name] ||= accumulated_stats
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Freeze and clear the list of accumulated stats, and add the
|
205
|
+
# aggregated stats to the unaccumulated stats.
|
206
|
+
#
|
207
|
+
def finish_accumulating
|
208
|
+
accumulated_stats_hash.each do |metric_name, stats|
|
209
|
+
stats.finish_accumulating
|
210
|
+
stats.freeze
|
211
|
+
end
|
212
|
+
accumulated_stats_hash.clear
|
213
|
+
end
|
214
|
+
|
215
|
+
def get_stats_no_scope_with_template_streaming(metric_name)
|
216
|
+
accumulated_stats_hash[metric_name] ||
|
217
|
+
get_stats_no_scope_without_template_streaming(metric_name)
|
218
|
+
end
|
219
|
+
|
220
|
+
def get_custom_stats_with_template_streaming(metric_name, stat_class)
|
221
|
+
accumulated_stats_hash[metric_name] ||
|
222
|
+
get_custom_stats_without_template_streaming(metric_name, stat_class)
|
223
|
+
end
|
224
|
+
|
225
|
+
def get_stats_with_template_streaming(metric_name, use_scope = true, scoped_metric_only = false)
|
226
|
+
key = scoped_metric_only || (use_scope && scope_name && scope_name != metric_name) ?
|
227
|
+
::NewRelic::MetricSpec.new(metric_name, scope_name) : metric_name
|
228
|
+
accumulated_stats_hash[key] ||
|
229
|
+
get_stats_without_template_streaming(metric_name, use_scope, scoped_metric_only)
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def accumulated_stats_hash
|
235
|
+
@accumulated_stats_hash ||= {}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# An AccumulatedMethodTraceStats is a proxy which aggregates the
|
241
|
+
# stats given to it, and updates the stats given to it on
|
242
|
+
# construction when #finish_accumulating is called.
|
243
|
+
#
|
244
|
+
# Example:
|
245
|
+
#
|
246
|
+
# acc = AccumulatedMethodTraceStats.new(stats)
|
247
|
+
# acc.trace_call(20, 10)
|
248
|
+
# acc.trace_call(20, 10)
|
249
|
+
# acc.finish_accumulating # calls stats.trace_call(40, 20)
|
250
|
+
#
|
251
|
+
class AccumulatedMethodTraceStats
|
252
|
+
def initialize(target_stats)
|
253
|
+
@target_stats = target_stats
|
254
|
+
end
|
255
|
+
|
256
|
+
def finish_accumulating
|
257
|
+
if @recorded_data_points
|
258
|
+
totals = aggregate(@recorded_data_points)
|
259
|
+
@target_stats.record_data_point(*totals)
|
260
|
+
end
|
261
|
+
if @traced_calls
|
262
|
+
totals = aggregate(@traced_calls)
|
263
|
+
@target_stats.trace_call(*totals)
|
264
|
+
end
|
265
|
+
@record_data_points = @traced_calls = nil
|
266
|
+
end
|
267
|
+
|
268
|
+
def record_data_point(call_time, exclusive_time = call_time)
|
269
|
+
recorded_data_points << [call_time, exclusive_time]
|
270
|
+
end
|
271
|
+
|
272
|
+
def trace_call(call_time, exclusive_time = call_time)
|
273
|
+
traced_calls << [call_time, exclusive_time]
|
274
|
+
end
|
275
|
+
|
276
|
+
# No need to aggregate this.
|
277
|
+
delegate :record_multiple_data_points, :to => '@target_stats'
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
def aggregate(data)
|
282
|
+
total_call_time = total_exclusive_time = 0
|
283
|
+
data.each do |call_time, exclusive_time|
|
284
|
+
total_call_time += call_time
|
285
|
+
total_exclusive_time += exclusive_time
|
286
|
+
end
|
287
|
+
[total_call_time, total_exclusive_time]
|
288
|
+
end
|
289
|
+
|
290
|
+
def recorded_data_points
|
291
|
+
@recorded_data_points ||= []
|
292
|
+
end
|
293
|
+
|
294
|
+
def traced_calls
|
295
|
+
@traced_calls ||= []
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
module MetricFrame
|
300
|
+
def self.included(base)
|
301
|
+
base.alias_method_chain :record_apdex, :template_streaming
|
302
|
+
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# Tell the MetricFrame to hold on to the times calculated during
|
306
|
+
# #record_apdex instead of adding the apdex value to the stats.
|
307
|
+
#
|
308
|
+
# Call #record_accumulated_apdex on another MetricFrame with
|
309
|
+
# this frame as an argument to record the total time.
|
310
|
+
#
|
311
|
+
def hold_apdex
|
312
|
+
@hold_apdex = true
|
313
|
+
end
|
314
|
+
|
315
|
+
def record_apdex_with_template_streaming(*args, &block)
|
316
|
+
return unless recording_web_transaction? && ::NewRelic::Agent.is_execution_traced?
|
317
|
+
ending = Time.now.to_f
|
318
|
+
if @hold_apdex
|
319
|
+
@held_summary_apdex = ending - apdex_start
|
320
|
+
@held_controller_apdex = ending - start
|
321
|
+
return
|
322
|
+
end
|
323
|
+
record_apdex_without_template_streaming(*args, &block)
|
324
|
+
end
|
325
|
+
|
326
|
+
attr_reader :held_summary_apdex, :held_controller_apdex
|
327
|
+
|
328
|
+
def record_accumulated_apdex(*previous_frames)
|
329
|
+
return unless recording_web_transaction? && ::NewRelic::Agent.is_execution_traced?
|
330
|
+
ending = Time.now.to_f
|
331
|
+
total_summary_apdex = previous_frames.map{|frame| frame.held_summary_apdex}.sum
|
332
|
+
total_controller_apdex = previous_frames.map{|frame| frame.held_controller_apdex}.sum
|
333
|
+
summary_stat = ::NewRelic::Agent.instance.stats_engine.get_custom_stats("Apdex", ::NewRelic::ApdexStats)
|
334
|
+
controller_stat = ::NewRelic::Agent.instance.stats_engine.get_custom_stats("Apdex/#{path}", ::NewRelic::ApdexStats)
|
335
|
+
self.class.update_apdex(summary_stat, total_summary_apdex + ending - apdex_start, exception)
|
336
|
+
self.class.update_apdex(controller_stat, total_controller_apdex + ending - start, exception)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
module ControllerInstrumentationShim
|
341
|
+
def self.included(base)
|
342
|
+
# This shim method takes the wrong number of args. Fix it.
|
343
|
+
base.module_eval 'def newrelic_metric_path(*args); end', __FILE__, __LINE__ + 1
|
344
|
+
end
|
345
|
+
|
346
|
+
# This is private in the real ControllerInstrumentation module,
|
347
|
+
# but we need it.
|
348
|
+
def _is_filtered?(key)
|
349
|
+
true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
module Histogram
|
354
|
+
def self.included(base)
|
355
|
+
base.alias_method_chain :process, :template_streaming
|
356
|
+
end
|
357
|
+
|
358
|
+
def start_accumulating
|
359
|
+
# Agent#reset_stats replaces #histogram with a fresh one, so
|
360
|
+
# we can't store accumulating response time in here. Store it
|
361
|
+
# in the agent instead.
|
362
|
+
agent.accumulated_histogram_time = 0
|
363
|
+
end
|
364
|
+
|
365
|
+
def finish_accumulating
|
366
|
+
process_without_template_streaming(agent.accumulated_histogram_time)
|
367
|
+
agent.accumulated_histogram_time = nil
|
368
|
+
end
|
369
|
+
|
370
|
+
def process_with_template_streaming(response_time)
|
371
|
+
if agent.accumulated_histogram_time
|
372
|
+
agent.accumulated_histogram_time += response_time
|
373
|
+
else
|
374
|
+
process_without_template_streaming(response_time)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
private
|
379
|
+
|
380
|
+
def agent
|
381
|
+
@agent ||= ::NewRelic::Agent.instance
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
module Agent
|
386
|
+
def start_accumulating(*metric_names)
|
387
|
+
stats_engine.start_accumulating(*metric_names)
|
388
|
+
histogram.start_accumulating
|
389
|
+
transaction_sampler.start_accumulating
|
390
|
+
end
|
391
|
+
|
392
|
+
def finish_accumulating
|
393
|
+
stats_engine.finish_accumulating
|
394
|
+
histogram.finish_accumulating
|
395
|
+
transaction_sampler.finish_accumulating
|
396
|
+
end
|
397
|
+
|
398
|
+
attr_accessor :accumulated_histogram_time
|
399
|
+
end
|
400
|
+
|
401
|
+
module TransactionSampler
|
402
|
+
def self.included(base)
|
403
|
+
base.alias_method_chain :notice_scope_empty, :template_streaming
|
404
|
+
end
|
405
|
+
|
406
|
+
def start_accumulating
|
407
|
+
@accumulated_samples = []
|
408
|
+
end
|
409
|
+
|
410
|
+
def finish_accumulating
|
411
|
+
supersample = merge_accumulated_samples or
|
412
|
+
return nil
|
413
|
+
@accumulated_samples = nil
|
414
|
+
|
415
|
+
# Taken from TransactionSampler#notice_scope_empty.
|
416
|
+
@samples_lock.synchronize do
|
417
|
+
@last_sample = supersample
|
418
|
+
|
419
|
+
@random_sample = @last_sample if @random_sampling
|
420
|
+
|
421
|
+
# ensure we don't collect more than a specified number of samples in memory
|
422
|
+
@samples << @last_sample if ::NewRelic::Control.instance.developer_mode?
|
423
|
+
@samples.shift while @samples.length > @max_samples
|
424
|
+
|
425
|
+
if @slowest_sample.nil? || @slowest_sample.duration < @last_sample.duration
|
426
|
+
@slowest_sample = @last_sample
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def notice_scope_empty_with_template_streaming(time=Time.now.to_f)
|
432
|
+
if @accumulated_samples
|
433
|
+
last_builder = builder or
|
434
|
+
return
|
435
|
+
last_builder.finish_trace(time)
|
436
|
+
@accumulated_samples << last_builder.sample
|
437
|
+
clear_builder
|
438
|
+
else
|
439
|
+
notice_scope_empty_without_template_streaming(time)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
def merge_accumulated_samples
|
446
|
+
return nil if @accumulated_samples.empty?
|
447
|
+
|
448
|
+
# The RPM transaction trace viewer only shows the first
|
449
|
+
# segment under the root segment. Move the segment trees of
|
450
|
+
# subsequent samples under that of the first one.
|
451
|
+
supersample = @accumulated_samples.shift.dup # samples have been frozen
|
452
|
+
supersample.incorporate(@accumulated_samples)
|
453
|
+
supersample
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
module TransactionSample
|
458
|
+
#
|
459
|
+
# Return a copy of this sample with the segment timestamps all
|
460
|
+
# incremented by the given delta.
|
461
|
+
#
|
462
|
+
# Note that although the returned object is a different
|
463
|
+
# TransactionSample instance, the segments will be the same
|
464
|
+
# objects, modified in place. We would modify the
|
465
|
+
# TransactionSample in place too, only this method is called on
|
466
|
+
# frozen samples.
|
467
|
+
#
|
468
|
+
def bump_by(delta)
|
469
|
+
root_segment.bump_by(delta)
|
470
|
+
sample = dup
|
471
|
+
sample.instance_eval{@start_time += delta}
|
472
|
+
sample
|
473
|
+
end
|
474
|
+
|
475
|
+
#
|
476
|
+
# Return the segment under the root.
|
477
|
+
#
|
478
|
+
# If the root segment has more than one child, raise an
|
479
|
+
# error. It appears this is never supposed to happen,
|
480
|
+
# though--the RPM transaction trace view only ever shows the
|
481
|
+
# first segment.
|
482
|
+
#
|
483
|
+
def subroot_segment
|
484
|
+
@subroot_segment ||=
|
485
|
+
begin
|
486
|
+
(children = @root_segment.called_segments).size == 1 or
|
487
|
+
raise Error, "multiple top segments found"
|
488
|
+
children.first
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
#
|
493
|
+
# Put the given samples under this one.
|
494
|
+
#
|
495
|
+
# The subroot children of the given samples are moved under this
|
496
|
+
# sample's subroot.
|
497
|
+
#
|
498
|
+
def incorporate(samples)
|
499
|
+
incorporated_duration = 0.0
|
500
|
+
samples.each do |sample|
|
501
|
+
# Bump timestamps by the total length of previous samples.
|
502
|
+
sample = sample.bump_by(root_segment.duration + incorporated_duration)
|
503
|
+
incorporated_duration += sample.root_segment.duration
|
504
|
+
|
505
|
+
# Merge segments.
|
506
|
+
sample.subroot_segment.called_segments.each do |segment|
|
507
|
+
subroot_segment.add_called_segment(segment)
|
508
|
+
end
|
509
|
+
|
510
|
+
# Merge params.
|
511
|
+
if (request_params = sample.params.delete(:request_params))
|
512
|
+
params[:request_params].reverse_merge!(request_params)
|
513
|
+
end
|
514
|
+
if (custom_params = sample.params.delete(:custom_params))
|
515
|
+
params[:custom_params] ||= {}
|
516
|
+
params[:custom_params].reverse_merge!(custom_params)
|
517
|
+
end
|
518
|
+
params.reverse_merge!(sample.params)
|
519
|
+
end
|
520
|
+
|
521
|
+
root_segment.exit_timestamp += incorporated_duration
|
522
|
+
subroot_segment.exit_timestamp += incorporated_duration
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
module Segment
|
527
|
+
attr_reader :called_segments
|
528
|
+
attr_writer :exit_timestamp
|
529
|
+
|
530
|
+
#
|
531
|
+
# Increment the timestamps by the given delta.
|
532
|
+
#
|
533
|
+
def bump_by(delta)
|
534
|
+
@entry_timestamp += delta
|
535
|
+
@exit_timestamp += delta
|
536
|
+
if @called_segments
|
537
|
+
@called_segments.each do |segment|
|
538
|
+
segment.bump_by(delta)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
ActionController::Dispatcher.middleware.insert(0, Middleware)
|
545
|
+
ActionController::Base.send :include, Controller
|
546
|
+
::NewRelic::Agent::StatsEngine.send :include, StatsEngine
|
547
|
+
::NewRelic::Agent::Instrumentation::MetricFrame.send :include, MetricFrame
|
548
|
+
::NewRelic::Agent::Instrumentation::ControllerInstrumentation::Shim.send :include, ControllerInstrumentationShim
|
549
|
+
::NewRelic::Histogram.send :include, Histogram
|
550
|
+
::NewRelic::Agent::Agent.send :include, Agent
|
551
|
+
::NewRelic::Agent::TransactionSampler.send :include, TransactionSampler
|
552
|
+
::NewRelic::TransactionSample.send :include, TransactionSample
|
553
|
+
::NewRelic::TransactionSample::Segment.send :include, Segment
|
554
|
+
end
|
555
|
+
end
|