template_streaming 0.0.11 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|